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 스크립트보다 두 배 빠릅니다.
노트:
플랫폼이 중요한 이유는 무엇입니까? 내 대답은
grep
과sed
간의 처리를 병렬 처리하기 때문입니다. 물론 편향적인 결과를 얻으려면 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 응답과 동일합니다.
- 여러 번 나타나지 않는 동일한 파일 이름 (예 :
awk 'script' foo bar foo
)과 여러 번 표시하려는 동일한 파일 이름에 의존합니다. - 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