bash - 커맨드라인 - 쉘 스크립트 파일 찾기




'n'또는 그 이하의 행을 포함하는 파일 나열 (7)

의문

폴더에서 n=27 줄 또는 줄 이하의 모든 .txt 파일의 이름을 인쇄하고 싶습니다. 나는 할 수있을 것이다

wc -l *.txt | awk '{if ($1 <= 27){print}}'

문제는 폴더에있는 많은 파일이 수백만 줄 (줄이 꽤 깁니다)이므로 wc -l *.txt 명령이 매우 느립니다. 원칙적으로 프로세스는 최소한 n 줄을 찾아 다음 파일로 진행할 때까지 줄 수를 계산할 수 있습니다.

빠른 대안이란 무엇입니까?

참고로 MAC OSX 10.11.6

시도

다음은 awk 사용한 시도입니다.

#!/bin/awk -f

function printPreviousFileIfNeeded(previousNbLines, previousFILENAME)
{
  if (previousNbLines <= n) 
  {
    print previousNbLines": "previousFILENAME
  }
}

BEGIN{
  previousNbLines=n+1
  previousFILENAME=NA
} 


{
  if (FNR==1)
  {
    printPreviousFileIfNeeded(previousNbLines, previousFILENAME)
    previousFILENAME=FILENAME
  }
  previousNbLines=FNR
  if (FNR > n)
  {
    nextfile
  }
}

END{
  printPreviousFileIfNeeded(previousNbLines, previousFILENAME)
}

다음과 같이 호출 할 수 있습니다.

awk -v n=27 -f myAwk.awk *.txt

그러나 코드는 완벽하게 빈 파일을 인쇄 할 때 실패합니다. 나는 그것을 고칠 방법이 확실하지 않고 나의 awk 스크립트가 갈 길이라고 확신하지 않는다.


GNU grep 사용한다면 (불행하게도 MacOSX> = 10.8은 파일이 아닌 -m-c 옵션이 BSD grep을 제공합니다), 여러분은이 흥미로운 대안을 발견 할 것입니다 (순수 awk 스크립트보다 빠름) :

grep -c -m28 -H ^ *.txt | sed '/:28$/ d; s/:[^:]*$//'

설명:

  • grep -c -m28 -H ^ *.txt 는 각 파일의 줄 수와 함께 각 파일의 이름을 출력하지만 결코 28 줄을 넘지 않습니다
  • sed '/:28$/ d; s/:[^:]*$//' sed '/:28$/ d; s/:[^:]*$//' 는 적어도 28 행을 가진 파일을 제거하고 다른 파일의 파일 이름을 출력합니다

대체 버전 : 병렬 처리 대신 순차 처리

res=$(grep -c -m28 -H ^ $files); sed '/:28$/ d; s/:[^:]*$//' <<< "$res"

벤치마킹

Ed Morton은이 답변이 awk 보다 빠르다는 나의 주장에 도전했습니다. 그는 자신의 대답에 몇 가지 벤치 마크를 추가했는데 결론을 내리지는 못했지만 자신이 올린 결과가 오도 된 것으로 생각하며 사용자와 시스템 시간에 관계없이 내 답변에 더 많은 벽시계를 보여줍니다. 그러므로 여기에 내 결과가있다.

먼저 테스트 플랫폼 :

  • Linux를 실행하는 4 코어 Intel i5 랩톱으로 OP 시스템 (Apple iMac)과 매우 유사합니다.

  • 평균적으로 ~ 400 줄이있는 100.000 텍스트 파일의 새로운 디렉토리. 총 640MB가 시스템 버퍼에 보관됩니다. 파일은 다음 명령으로 작성되었습니다.

    for ((f = 0; f < 100000; f++)); do echo "File $f..."; for ((l = 0; l < RANDOM & 1023; l++)); do echo "File $f; line $l"; done > file_$f.txt; done

결과 :

  • grep + sed (이 응답) : 561 ms 경과, 586 ms 사용자 + sys
  • grep + sed (이 답변, 순차적 버전) : 678ms 경과, 688ms 사용자 + sys
  • awk (Ed Morton) : 1050ms 경과, 1036ms 사용자 + sys
  • awk (삼중 쌍) : 1137ms 경과, 1123ms 사용자 + sys
  • awk (anubhava) : 1150ms 경과, 1137ms 사용자 + sys
  • awk (kvantour) : 1280ms 경과, 1266ms 사용자 + sys
  • 파이썬 (조이 해링턴) : 1543ms 경과, 1537ms 사용자 + sys
  • + xargs + sed (agc) 찾기 : 91 초 경과, 10 초 user + sys
  • for + awk (Jeff Schaller) : 247 초 경과, 83 초 user + sys
  • + bash + grep (hek2mgl) 찾기 : 356 초 경과, 116 초 user + sys

결론:

필자는 OP 컴퓨터와 비슷한 유닉스 멀티 코어 노트북을 사용하여 정확한 결과를 얻는 것이 가장 빠르다. 내 컴퓨터에서, 그것은 가장 빠른 awk 스크립트보다 두 배 빠릅니다.

노트:

  • 플랫폼이 중요한 이유는 무엇입니까? 내 대답은 grepsed 간의 처리를 병렬 처리하기 때문입니다. 물론 편향적인 결과를 얻으려면 CPU 코어 (VM?) 하나 또는 CPU 할당과 관련한 OS의 다른 제한 사항이있는 경우 대체 (순차적) 버전을 벤치마킹해야합니다.

  • 분명히 벽에 걸린 시간에만 결론을 내릴 수는 없습니다. CPU를 요구하는 동시 프로세스의 수와 시스템의 코어 수를 비교하기 때문입니다. 따라서 사용자 + sys 타이밍을 추가했습니다.

  • 그 타이밍은 명령이 1 분 이상 걸릴 때를 제외하고는 20 회 이상의 평균입니다 (1 회만 실행)

  • 10 초 미만의 모든 응답의 경우 쉘이 *.txt 를 처리하는 데 걸리는 시간은 무시할 수 없으므로 파일 목록을 전처리하고 변수에 넣은 다음 명령 내용에 변수 내용을 추가합니다. 벤치마킹이었다.

  • 모든 대답은 1. 결과를 argv[0] ( "awk")가 포함 된 3 인의 대답 (내 테스트에서 고정됨); 2. kvantour의 대답은 빈 파일 만 나열한 것입니다 ( -vn=27 고정). 3. 빈 파일 (고정되지 않음)을 놓친 find + sed 응답.

  • 나는 GNU sed 4.5가 없기 때문에 ctac_의 답을 테스트 할 수 없었다. 아마도 가장 빠른 것일뿐 아니라 빈 파일을 그리워합니다.

  • python 대답은 파일을 닫지 않습니다. 먼저 ulimit -n hard 해야했습니다.


GNU awk with nextfile과 ENDFILE :

awk -v n=27 'FNR>n{f=1; nextfile} ENDFILE{if (!f) print FILENAME; f=0}' *.txt

모든 awk 사용 :

awk -v n=27 '
    { fnrs[FILENAME] = FNR }
    END {
        for (i=1; i<ARGC; i++) {
            filename = ARGV[i]
            if ( fnrs[filename] < n ) {
                print filename
            }
        }
    }
' *.txt

이들은 입력 파일이 비 었는지 여부에 관계없이 둘 다 작동합니다. 비 gawk 버전에 대한주의 사항은 다른 현재 awk 응답과 동일합니다.

  1. 여러 번 나타나지 않는 동일한 파일 이름 (예 : awk 'script' foo bar foo )과 여러 번 표시하려는 동일한 파일 이름에 의존합니다.
  2. arg 목록에 변수가 설정되어 있지 않아야합니다 (예 : awk 'script' foo FS=, bar )

gawk 버전에는 이러한 제한이 없습니다.

최신 정보:

위의 GNU awk 스크립트와 xhienne이 게시 한 GNU grep + sed 스크립트 사이의 타이밍을 테스트하기 위해 그녀의 솔루션은 faster than a pure awk script 것이라고 말했기 때문에 나는 10,000 개의 입력 파일을 만들었습니다. 이 스크립트 :

$ awk -v numFiles=10000 -v maxLines=1000 'BEGIN{for (i=1;i<=numFiles;i++) {numLines=int(rand()*(maxLines+1)); out="out_"i".txt"; printf "" > out; for (j=1;j<=numLines; j++) print ("foo" j) > out} }'

그리고 그들에 2 개의 명령을 달리고이 제 3의 달린 타이밍 결과를 얻었다 :

$ time grep -c -m28 -H ^ *.txt | sed '/:28$/ d; s/:[^:]*$//' > out.grepsed

real    0m1.326s
user    0m0.249s
sys     0m0.654s

$ time awk -v n=27 'FNR>n{f=1; nextfile} ENDFILE{if (!f) print FILENAME; f=0}' *.txt > out.awk

real    0m1.092s
user    0m0.343s
sys     0m0.748s

두 스크립트 모두 동일한 출력 파일을 생성했습니다. 위는 cygwin에서 bash로 실행되었습니다. 다른 시스템에서 타이밍 결과가 약간 다를 수 있지만 차이는 항상 무시할 수있을 것으로 기대합니다.

한 행에 최대 20 개의 임의 문자를 10 줄 인쇄하려면 (주석 참조) :

$ maxChars=20
    LC_ALL=C tr -dc '[:print:]' </dev/urandom |
    fold -w "$maxChars" |
    awk -v maxChars="$maxChars" -v numLines=10 '
        { print substr($0,1,rand()*(maxChars+1)) }
        NR==numLines { exit }
    '
0J)-8MzO2V\XA/o'qJH
@r5|g<WOP780
^[email protected]\
vP{l^pgKUFH9
-6r&]/-6dl}pp W
&.UnTYLoi['2CEtB
Y~wrM3>4{
^F1mc9
?~NHh}a-EEV=O1!y
of

awk 내에서 모든 것을 수행하려면 (훨씬 느려질 것입니다) :

$ cat tst.awk
BEGIN {
    for (i=32; i<127; i++) {
        chars[++charsSize] = sprintf("%c",i)
    }
    minChars = 1
    maxChars = 20
    srand()
    for (lineNr=1; lineNr<=10; lineNr++) {
        numChars = int(minChars + rand() * (maxChars - minChars + 1))
        str = ""
        for (charNr=1; charNr<=numChars; charNr++) {
            charsIdx = int(1 + rand() * charsSize)
            str = str chars[charsIdx]
        }
        print str
    }
}

$ awk -f tst.awk
Heer H{QQ?qHDv|
Psuq
Ey`-:O2v7[]|N^EJ0
j#@/y>CJ3:=3*b-joG:
?
^|O.[tYlmDo
TjLw
`2Rs=
!('IC
hui

개별적으로 awk를 호출해야한다면 28 행에서 중지하도록 요청하십시오.

for f in ./*.txt
do
  if awk 'NR > 27 { fail=1; exit; } END { exit fail; }' "$f"
  then
    printf '%s\n' "$f"
  fi
done

awk 변수의 기본값은 0입니다. 따라서 28 행에 도달하지 않으면 exit 코드가 0이되어 if 테스트가 성공적으로 이루어 지므로 파일 이름이 인쇄됩니다.


당신은 작은 bash 인라인 스크립트의 도움으로 find 를 사용할 수 있습니다 :

find -type f -exec bash -c '[ $(grep -cm 28 ^ "${1}") != "28" ] && echo "${1}"' -- {} \;

[ $(grep -cm 28 ^ "${1}") != "28" ] && echo "${1}" 은 grep을 사용하여 최대 28 번까지 줄의 시작 부분 ( ^ )을 찾습니다. 이 명령이! = "28"을 반환하면 파일의 행 수가 28 개보다 적어야합니다.


이건 어떻습니까?

awk 'BEGIN { for(i=1;i<ARGC; ++i) arg[ARGV[i]] }
  FNR==28 { delete arg[FILENAME]; nextfile }
  END { for (file in arg) print file }' *.txt

파일 이름 인수 목록을 연관 배열에 복사 한 다음 28 번째 줄이있는 모든 파일을 제거합니다. 빈 파일은 분명히이 조건과 일치하지 않으므로 마지막에는 빈 줄을 포함하여 줄 수가 적은 모든 파일이 남습니다.

nextfile 은 많은 Awk 변종에서 흔히 사용되는 확장 프로그램이었고 2012 년 POSIX에 의해 성문화되었습니다. 정말로 오래된 공룡 OS (또는 천국, 아마도 Windows), 행운을 빌며 GNU Awk를 사용해보십시오.


줄 수가 27 이상이되면 바로 다음 파일로 이동하는이 awk 를 시도 할 수 있습니다.

awk -v n=27 'BEGIN{for (i=1; i<ARGC; i++) f[ARGV[i]]}
FNR > n{delete f[FILENAME]; nextfile}
END{for (i in f) print i}' *.txt

awk 는 파일을 한 줄씩 처리하므로 줄 수를 얻기 위해 전체 파일을 읽지 않습니다.


awk 가 진행하는 가장 흥미로운 방법 인 것처럼 보이지만, 이미 triplee , anubhava 및 Ed Morton 의 기존 솔루션에 또 다른 것이 있습니다. triplee 및 anubhava의 솔루션에 nextfile 문을 사용하고 Ed Morton의 POSIX 증명 솔루션이 전체 파일을 읽는 경우 전체 파일을 읽지 않는 솔루션을 제공합니다.

awk -v n=27 'BEGIN{ for(i=1;i<ARGC;++i) {
                       j=0; fname=ARGV[i];
                       while( ((getline < fname) > 0 ) && j<=n) { j++ }
                       if(j<=n) print fname; close(fname)
                  }
                  exit
             }' *.txt




awk