NON GNU awk를 사용하여 수정 사항 저장


9

OP가 Edit_file 자체에 작업을 편집하고 저장 해야하는 질문 (SO 자체)을 발견했습니다.

우리가 할 수있는 단일 Input_file에 대해 알고 있습니다.

awk '{print "test here..new line for saving.."}' Input_file > temp && mv temp Input_file

이제 같은 종류의 파일 형식으로 변경해야한다고 가정합니다 (여기서는 .txt라고 가정).

내가이 문제에 대해 시도 / 생각한 : .txt 파일의 for 루프를 통과하고 싱글을 호출awk하는 것은 고통스럽고 권장하지 않는 프로세스입니다. 불필요한 CPU 사이클을 낭비하고 더 많은 파일 수에 대해서는 더 많을 것입니다 느린.

따라서 awkinplace 옵션을 지원하지 않는 NON GNU로 여러 파일에 대한 내부 편집을 수행하기 위해 여기서 수행 할 수있는 작업은 무엇입니까 ? 나는이 스레드를 통해 awk를 사용하여 수정 사항을 저장 하지만 GNU awk이외의 awk에는 inplace옵션 이 없기 때문에 NON GNU awk vice와 그 자체로 여러 파일을 변경하는 것은별로 없습니다 .

참고 :bash 응답 부분에서 bash 명령을 사용하여 임시 파일의 이름을 실제 Input_file 이름으로 바꾸어추가했기 때문에 태그를추가하는 이유는 무엇입니까?



편집 : Ed sir의 의견에 따라 샘플 예제를 추가하지만이 스레드 코드의 목적은 일반적인 목적의 내부 편집에서도 사용할 수 있습니다.

샘플 입력 파일 :

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

예상 출력 샘플 :

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2


1
@ RavinderSingh13이 파일을 적용 할 파일이 많으면 awk(아마도 서브 쉘에서) 단일 그룹 호출을 사용 {...}하고 결과를 원하는 출력 파일 (각 입력 파일, 또는 모든 입력 파일에 대해 결합 된 파일). 그런 다음 단순히 서브 쉘 또는 중괄호로 묶은 그룹의 출력을 현재 파일에 기록하고 있습니까? awk명령 뒤에 입력 파일 문자열을 포함 시키면 모든 파일 (또는 유사한 파일)이 순차적으로 처리됩니다.
David C. Rankin

@ DavidC.Rankin, 이것에 답장을 보내 주셔서 감사합니다. 그래, 나는 당신이 말하는 비슷한 종류의 물건을 올렸습니다. 내 대답은이 질문에 게시되어 있습니다. lemme은 같은 선생님에 대한 당신의 견해를 알고 있습니다.
RavinderSingh13

1
잠을 자고 그것에 대해 생각한 후 awk {..} file1 .. fileX수정 된 파일 을 작성하는 두 가지 옵션 (1)을 볼 수 있습니다 . 예를 들어 temp01다음 파일을 처리하는 동안 다음 반복에서 a mv -f tmp01 input01를 사용 하여 입력 파일을 수정 된 데이터로 덮어 씁니다. 또는 (2) 단순히 스크립트를 ./tmp/tmp01 ... ./tmp/tmp0X실행 하는 동안 새 디렉토리를 작성하고 디렉토리의 awk파일에 대한 루프와 함께 후속 조치를 수행합니다 ( ./tmp예 : mv -f "$i" "input_${i##*[^0-9]}"이전 입력 파일을 바꾸는 데 필요한 확장)
David C. Rankin

@ DavidC.Rankin, 뷰를 여기에 알려 주셔서 감사합니다. IMHO 첫 번째 옵션은 awk완전한 코드 완성 없이 무언가를하고 있기 때문에 약간의 위험이있을 수 있습니다. 두 번째 옵션은 내 제안에서 사용하는 것과 거의 동일합니다. 당신이 그 해결책에 대해 당신의 생각을 알릴 수 있다면 감사하십시오.
RavinderSingh13

답변:


6

이 스레드의 주요 목표는 비 GNU에서 SAVE를 배치하는 방법 awk이므로 모든 종류의 요구 사항에 도움이 될 템플릿을 먼저 게시하고 있으므로 코드에 추가 / 추가 BEGINEND섹션을 작성하여 기본 BLOCK을 유지해야합니다. 요구 사항과 그 자리에서 편집해야합니다.

참고 : 다음은 모든 출력을 output_file에 기록하므로 표준 출력으로 아무것도 인쇄하려면다음을print...따르지 않고 명령문만 추가하십시오> (out).

일반 템플릿 :

awk -v out_file="out" '
FNR==1{
close(out)
out=out_file count++
rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
    .....your main block code.....
}
END{
 if(rename){
   system(rename)
 }
}
' *.txt


제공되는 특정 샘플 솔루션 :

나는 awk그 자체 내에서 다음과 같은 접근법을 생각해 냈습니다 (추가 된 샘플은 이것을 해결하고 Input_file 자체에 출력을 저장하는 접근법입니다)

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print FNR > (out)
}
END{
  if(rename){
    system(rename)
  }
}
' *.txt

참고 : 이것은 편집 된 출력을 Input_file 자체에 저장하기위한 테스트 일뿐입니다. 프로그램의 END 섹션과 함께 BEGIN 섹션을 사용할 수 있으며 주 섹션은 특정 질문 자체의 요구 사항에 따라야합니다.

공정한 경고 : 또한이 접근법은 경로에 새로운 임시 출력 파일을 생성하므로 시스템에 충분한 공간이 있는지 확인하십시오. 최종 결과에서는 기본 Input_file 만 유지되지만 작업 중에는 시스템 / 디렉토리에 공간이 필요합니다



다음은 위 코드에 대한 테스트입니다.

예를 들어 프로그램 실행 : 다음은.txtInput_file이라고 가정합니다.

cat << EOF > test1.txt
onetwo three
tets testtest
EOF

cat << EOF > test2.txt
onetwo three
tets testtest
EOF

cat << EOF > test3.txt
onetwo three
tets testtest
EOF

이제 다음 코드를 실행할 때

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print "new_lines_here...." > (out)
}
END{
  if(rename){
    system("ls -lhtr;" rename)
  }
}
' *.txt

참고 : 나는의도적으로 섹션ls -lhtr에서system어떤 출력 파일을 만들고 있는지 (임시 기준) 나중에 실제 이름으로 바꿀 것이기 때문에 배치했습니다.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out2
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out1
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out0

우리가 실행 ls -lhtr후 after awkscript를 수행하면 .txt거기에 파일 만 볼 수 있습니다.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt


설명 : 여기에 위 명령에 대한 자세한 설명을 추가하십시오.

awk -v out_file="out" '                                    ##Starting awk program from here, creating a variable named out_file whose value SHOULD BE a name of files which are NOT present in our current directory. Basically by this name temporary files will be created which will be later renamed to actual files.
FNR==1{                                                    ##Checking condition if this is very first line of current Input_file then do following.
  close(out)                                               ##Using close function of awk here, because we are putting output to temp files and then renaming them so making sure that we shouldn't get too many files opened error by CLOSING it.
  out=out_file count++                                     ##Creating out variable here, whose value is value of variable out_file(defined in awk -v section) then variable count whose value will be keep increment with 1 whenever cursor comes here.
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"     ##Creating a variable named rename, whose work is to execute commands(rename ones) once we are done with processing all the Input_file(s), this will be executed in END section.
}                                                          ##Closing BLOCK for FNR==1  condition here.
{                                                          ##Starting main BLOCK from here.
  print "new_lines_here...." > (out)                       ##Doing printing in this example to out file.
}                                                          ##Closing main BLOCK here.
END{                                                       ##Starting END block for this specific program here.
  if(rename){                                              ##Checking condition if rename variable is NOT NULL then do following.
    system(rename)                                         ##Using system command and placing renme variable inside which will actually execute mv commands to rename files from out01 etc to Input_file etc.
  }
}                                                          ##Closing END block of this program here.
' *.txt                                                    ##Mentioning Input_file(s) with their extensions here.

1
재미있는 사실 : FNR==1블록 에서 입력 파일을 삭제 하면 변경 사항을 그대로 저장할 수 있습니다. 처럼 awk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files.... 이것은 전혀 신뢰할 수 없지만 (완전한 데이터 손실이 발생할 가능성이 있음) 여전히 여전히 잘 작동합니다 : D
oguz ismail

1
매우 잘 설명 된 해결 방법
anubhava

3

내가 이것을하려고한다면 아마도 이런 식으로 갈 것입니다 :

$ cat ../tst.awk
FNR==1 { saveChanges() }
{ print FNR > new }
END { saveChanges() }

function saveChanges(   bak, result, mkBackup, overwriteOrig, rmBackup) {
    if ( new != "" ) {
        bak = old ".bak"
        mkBackup = "cp \047" old "\047 \047" bak "\047; echo \"$?\""
        if ( (mkBackup | getline result) > 0 ) {
            if (result == 0) {
                overwriteOrig = "mv \047" new "\047 \047" old "\047; echo \"$?\""
                if ( (overwriteOrig | getline result) > 0 ) {
                    if (result == 0) {
                        rmBackup = "rm -f \047" bak "\047"
                        system(rmBackup)
                    }
                }
            }
        }
        close(rmBackup)
        close(overwriteOrig)
        close(mkBackup)
    }
    old = FILENAME
    new = FILENAME ".new"
}

$ awk -f ../tst.awk test1.txt test2.txt test3.txt

원본 파일을 백업에 먼저 복사 한 다음 원본에 변경 사항을 저장하는 것을 선호했지만 그렇게하면 바람직하지 않은 모든 입력 파일의 FILENAME 변수 값이 변경됩니다.

원본 파일이 디렉토리에 whatever.bak있거나 whatever.new디렉토리에있는 경우 임시 파일로 덮어 쓰므로 테스트도 추가해야합니다. mktemp임시 파일 이름을 얻기 위한 호출 이 더 강력합니다.

이 상황에서 FAR보다 유용한 것은 POSIX sed, awk, grep, tr에 대한 "inplace"편집을 제공하는 데 사용될 수 있기 때문에 다른 명령을 실행하고 "inplace"편집 부분을 수행하는 도구입니다. print > out값을 인쇄 할 때마다 스크립트 구문을 등 으로 변경할 필요가 없습니다 . 간단하고 깨지기 쉬운 예 :

$ cat inedit
#!/bin/env bash

for (( pos=$#; pos>1; pos-- )); do
    if [[ -f "${!pos}" ]]; then
        filesStartPos="$pos"
    else
        break
    fi
done

files=()
cmd=()
for (( pos=1; pos<=$#; pos++)); do
    arg="${!pos}"
    if (( pos < filesStartPos )); then
        cmd+=( "$arg" )
    else
        files+=( "$arg" )
    fi
done

tmp=$(mktemp)
trap 'rm -f "$tmp"; exit' 0

for file in "${files[@]}"; do
    "${cmd[@]}" "$file" > "$tmp" && mv -- "$tmp" "$file"
done

다음과 같이 사용하십시오.

$ awk '{print FNR}' test1.txt test2.txt test3.txt
1
2
1
2
1
2

$ ./inedit awk '{print FNR}' test1.txt test2.txt test3.txt

$ tail test1.txt test2.txt test3.txt
==> test1.txt <==
1
2

==> test2.txt <==
1
2

==> test3.txt <==
1
2

해당 inedit스크립트의 명백한 문제점 중 하나 는 입력 파일이 여러 개인 경우 명령과 별도로 입력 / 출력 파일을 식별하기 어렵다는 것입니다. 위의 스크립트는 모든 입력 파일이 명령의 끝에 목록으로 표시되고 명령이 한 번에 하나씩 실행된다고 가정하지만 물론 두 개 이상의 파일이 필요한 스크립트에는 사용할 수 없습니다 시간, 예 :

awk 'NR==FNR{a[$1];next} $1 in a' file1 file2

또는 arg 목록의 파일 사이에 변수를 설정하는 스크립트, 예 :

awk '{print $7}' FS=',' file1 FS=':' file2

독자를위한 연습으로 남겨 두었지만, xargs시놉시스가 어떻게 inedit작동 해야하는지에 대한 시작점으로 생각하십시오. :-).


0

쉘 솔루션은 간단하고 충분히 빠릅니다.

for f in *.txt
do  awk '...' $f > $f.tmp
    mv $f.tmp $f
done

이것이 너무 느리다는 것을 결정적으로 입증 한 경우에만 다른 솔루션을 검색하십시오. 기억하십시오 : 조기 최적화는 모든 악의 근원입니다.


답장을 보내 주셔서 감사하지만 내 질문 자체에서 언급했듯이 우리는이 답변을 알고 있지만 실제로이 작업을 수행하는 데 너무 많은 어려움을 겪고 있으므로 awk 자체에서 무언가를 시도 할 수 있다고 언급 한 이유입니다. 시간 내 주셔서 감사합니다.
RavinderSingh13
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.