XARGS와 함께 AWK를 사용할 때 발생하는 문제

XARGS와 함께 AWK를 사용할 때 발생하는 문제

다음 명령을 실행하는 데 문제가 있습니다 -->

find . -type f -name 'out*' |
  xargs awk 'BEGIN{print "Filename, Energy"}/TOTAL ENERGY/{print FILENAME, "," $4}' >> energy.csv

출력 파일의 모든 디렉터리를 살펴보고 에너지를 구문 분석한 다음 헤더 열과 함께 Energy.csv 파일에 쓰길 원합니다.

문제는 파일 중간에 헤더 열을 여러 번 쓰는 경우가 있지만 항상 그런 것은 아니라는 것입니다. 나는이 행동을 이해하지 못합니다.

답변1

xargs(또는 find)은 사용자가 지시한 명령을 호출하여 한 번에 원하는 만큼의 파일 이름을 전달합니다. 항상 ARG_MAX오버런을 일으키는 것보다 적습니다.

따라서 awk 스크립트는 여러 배치의 입력 파일과 함께 호출되며 BEGINawk가 호출될 때마다 해당 부분이 실행됩니다. 실행을 시작하기 전에 awk 스크립트 외부에서 헤더 라인의 초기 인쇄를 수행하여 이 문제를 피할 수 있습니다 find.

따라서 다음을 수행하십시오.

{
    ofs=','
    printf '%s%s%s\n' 'Filename' "$ofs" 'Energy' &&
    find . -type f -name 'out*' |
        xargs awk -v OFS="$ofs" '/TOTAL ENERGY/{print FILENAME, $4}'
} > energy.csv

또는 다음과 같습니다( 명령 자체를 호출할 수 있으므로 출력을 덜 강력한 것으로 find파이프할 필요가 없습니다 ).xargs

{
    ofs=','
    printf '%s%s%s\n' 'Filename' "$ofs" 'Energy' &&
    find . -type f -name 'out*' -exec \
        awk -v OFS="$ofs" '/TOTAL ENERGY/{print FILENAME, $4}' {} +
} > energy.csv

또한 awk 부분을 좀 더 관용적으로 만들고 ,헤더의 s 뒤와 ,나머지 출력의 s 앞의 가짜 공백을 제거했습니다.

답변2

xargs(cross-args의 경우)는 입력에서 단어를 읽고 이를 명령 cross으로 arg전달하는 명령입니다.

그 입력은 무한히 길 수 있지만 명령에 전달할 수 있는 인수의 개수는 제한되어 있고 그렇지 않더라도 인수 목록을 전체적으로 전달해야 하기 때문에 연속적인 인수를 전달하고 싶지 않습니다. 입력에 단어 스트림이 있으면 xargs모든 읽기를 전달하고 모두 메모리에 저장하며 입력 끝에 도달한 경우에만 명령을 시작해야 합니다(있는 경우).

또한 find단어 목록(여기서는 파일 경로)이 기본적으로 예상되는 형식으로 생성되지 않습니다 xargs. 그것들을 서로 연결하려면 표준이 필요 find ... -print0 | xargs -r0 cmd...하거나 단지 필요합니다 find ... -exec cmd... {} +.

따라서 파일 목록이 충분히 크면 xargs일반적으로 (귀하의 경우) 여러 번 실행되고 cmd해당 명령문은 매번 실행됩니다.awkawkBEGIN

많은 GNU 명령( wc, sort, ...)에는 최근 옵션(또는 GNU 또는 GNU 의 조건자 ) du이 추가되어 파일의 NUL 구분 기호나 표준 입력(이와 같은)을 인수로 처리하기 위해 파일 목록을 가져옵니다. 제한을 피하고 전체 목록을 메모리에 저장하지 않으며 표준 입력에서 파일을 읽는 즉시 파일 처리를 시작할 수 있음을 의미합니다.--files0-from-files0-fromfind--null --verbatim-files-from --files-fromtarxargs -r0

예를 들어,

find . -name '*.txt' -type f -print0 | wc --files0-from - -w --total=always

파일을 찾으면 w파일의 주문 수를 인쇄하고 끝에 한 줄을 인쇄합니다. 이는 동시에 실행되지 않는 where보다 훨씬 좋으며 where는 여러 줄을 출력할 수 있습니다..txtfindtotalfind . -name '*.txt' -type f -exec wc -w --total=always {} +findwctotal

GNU에는 awk아직 그러한 옵션이 없지만 다음을 사용하여 직접 구현할 수 있습니다.

find . -type f -name 'out*' -print0 | sort -Vz |
  gawk '
    function inputfile(  old_RS,ret) {
      if (ARGC > 1) delete ARGV[ARGC - 1]
      old_RS = RS
      RS = "\0"
      ret = getline ARGV[ARGC++] < "-"
      RS = old_RS
      if (ret <= 0) exit(-ret)
    }
    BEGIN  {inputfile()}
    ENDFILE{inputfile()}

    # then your awk script
    BEGIN{
      OFS = ","
      print "Filename", "Energy"
    }
    /TOTAL ENERGY/ {print FILENAME, $4}' >> energy.csv

awk(이 특별한 경우에는 해당 헤더를 다음 외부에 인쇄하는 것이 훨씬 간단합니다.에드가 보여줬어).

다음과 동일하며 다음과 perl -lan같습니다.

find . -type f -name 'out*' -print0 | sort -Vz |
  perl -lane '
    sub nextfile {
      local $/ = "\0";
      my $file = <STDIN> or exit;
      shift @ARGV;
      push @ARGV, $file
    }
    BEGIN {nextfile}

    BEGIN {$, = ","; print "Filename", "Energy"}
    print $ARGV, $F[3] if /TOTAL ENERGY/;

    nextfile if eof'

관련 정보