큰 파일에서 고양이 줄 X를 줄 Y로


132

큰 텍스트 파일 (> 2GB)이 있고 catXY원한다고 가정합니다 (예 : 57890000 ~ 57890010).

내가 이해 한 것에서 나는 배관 head을 통해 tail또는 그 반대로 배관함으로써 이것을 할 수있다.

head -A /path/to/file | tail -B

또는 대안 적으로

tail -C /path/to/file | head -D

여기서 A, B, CD파일의 라인 수로부터 계산 될 수있다, XY.

그러나이 방법에는 두 가지 문제가 있습니다.

  1. 당신은 계산해야 A, B, CD.
  2. 명령은 내가 읽고 싶은 것보다 더 많은pipe을 서로에게 줄 수 있습니다 (예 : 거대한 파일 중간에 몇 줄만 읽는 경우)

쉘이 작동하고 원하는 줄을 출력하는 방법이 있습니까? (만 제공하는 동안 XY)?


1
참고로, 6 가지 방법의 실제 속도 테스트 비교가 내 대답에 추가되었습니다.
Kevin

답변:


119

나는 sed해결책을 제안 하지만 완전성을 위해

awk 'NR >= 57890000 && NR <= 57890010' /path/to/file

마지막 줄을 잘라내려면 :

awk 'NR < 57890000 { next } { print } NR == 57890010 { exit }' /path/to/file

속도 테스트 :

  • 에 의해 생성 된 100,000,000 줄 파일 seq 100000000 > test.in
  • 리딩 라인 50,000,000-50,000,010
  • 특별한 순서가없는 테스트
  • realbash의 내장에 의해보고 된 시간time
 4.373  4.418  4.395    tail -n+50000000 test.in | head -n10
 5.210  5.179  6.181    sed -n '50000000,50000010p;57890010q' test.in
 5.525  5.475  5.488    head -n50000010 test.in | tail -n10
 8.497  8.352  8.438    sed -n '50000000,50000010p' test.in
22.826 23.154 23.195    tail -n50000001 test.in | head -n10
25.694 25.908 27.638    ed -s test.in <<<"50000000,50000010p"
31.348 28.140 30.574    awk 'NR<57890000{next}1;NR==57890010{exit}' test.in
51.359 50.919 51.127    awk 'NR >= 57890000 && NR <= 57890010' test.in

이것들은 정확한 벤치 마크는 아니지만, 각 명령의 상대 속도를 잘 이해할 수있을만큼 차이가 명확하고 반복 가능합니다 *.

* : 처음 두 사이를 제외 sed -n p;q하고 head|tail는 기본적으로 동일한 것으로 보인다.


11
호기심에서 : 테스트 사이에 디스크 캐시를 어떻게 플러시 했습니까?
Paweł Rumian

2
무엇에 대해 tail -n +50000000 test.in | head -n10달리하는 tail -n-50000000 test.in | head -n10올바른 결과를 줄 것이다?
Gilles

4
좋아, 나는 가서 벤치 마크를했다. 꼬리는 머리보다 sed보다 빠르며, 그 차이는 예상보다 훨씬 큽니다.
Gilles

3
@Gills 당신 말이 맞아, 내 나쁜. tail+|headsed보다 10-15 % 빠릅니다. 저는 그 벤치 마크를 추가했습니다.
케빈

1
질문에 줄이 필요하다는 것을 알고 있지만 -c문자를 건너 뛰려면를 사용하면 tail+|head즉각적입니다. 물론 "50000000"이라고 말할 수 없으며 원하는 섹션의 시작 부분을 수동으로 검색해야 할 수도 있습니다.
Danny Kirchmeier

51

X에서 Y까지의 행을 포함 시키려면 (1부터 번호 매기기 시작)

tail -n +$X /path/to/file | head -n $((Y-X+1))

tail첫 번째 X-1 줄을 읽고 버리고 (그 주위에 방법은 없습니다) 다음 줄을 읽고 인쇄합니다. head요청 된 줄 수를 읽고 인쇄 한 다음 종료합니다. 되면 head종료하면 tail수신 SIGPIPE의 신호와 다이하므로 입력 파일로부터 라인 버퍼 크기 분량 (통상적으로 몇 킬로바이트)보다 자세히 없을 것이다.

또는 gorkypl이 제안한 것처럼 sed를 사용하십시오.

sed -n -e "$X,$Y p" -e "$Y q" /path/to/file

sed 솔루션은 속도가 상당히 느립니다 (적어도 GNU 유틸리티 및 Busybox 유틸리티의 경우, 파이프가 느리고 sed가 빠른 OS에서 파일의 많은 부분을 추출하면 sed가 더 경쟁력이있을 수 있습니다). 리눅스에서의 빠른 벤치 마크는 다음과 같습니다. 에 의해 생성 된 데이터 seq 100000000 >/tmp/a, 환경은 Linux / amd64, /tmptmpfs 및 시스템은 유휴 상태이며 스왑되지 않습니다.

real  user  sys    command
 0.47  0.32  0.12  </tmp/a tail -n +50000001 | head -n 10 #GNU
 0.86  0.64  0.21  </tmp/a tail -n +50000001 | head -n 10 #BusyBox
 3.57  3.41  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #GNU
11.91 11.68  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #BusyBox
 1.04  0.60  0.46  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #GNU
 7.12  6.58  0.55  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #BusyBox
 9.95  9.54  0.28  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #GNU
23.76 23.13  0.31  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #BusyBox

작업하려는 바이트 범위를 알고 있으면 시작 위치로 바로 건너 뛰어 더 빠르게 추출 할 수 있습니다. 그러나 줄의 경우 처음부터 읽고 줄 바꿈을 계산해야합니다. 블록 크기가 b 인 0부터 시작하여 x부터 y 독점까지의 블록을 추출하려면 다음을 수행하십시오.

dd bs=$b seek=$x count=$((y-x)) </path/to/file

1
사이에 캐싱이 없습니까? 꼬리와 sed의 차이가 나에게 너무 커 보입니다.
Paweł Rumian

@ gorkypl 몇 가지 조치를 취했으며 시간이 비슷했습니다. 내가 쓴 것처럼, 이것은 RAM에서 일어나고 있습니다 (모든 것이 캐시에 있습니다).
Gilles

1
@Gilles tail will read and discard the first X-1 line는 끝에서부터 줄 수가 주어질 때 피하는 것 같습니다. 이러한 경우 tail은 실행 시간에 따라 끝에서 뒤로 읽히는 것처럼 보입니다. 읽어보십시오 : http://unix.stackexchange.com/a/216614/79743.

1
@BinaryZebra 예, 입력이 정규 파일 인 경우 tail(GNU tail을 포함한) 일부 구현 은 휴리스틱을 끝에서 읽을 수 있습니다. tail | head다른 방법에 비해 솔루션 이 향상됩니다 .
Gilles

22

head | tail접근 방식은이를 수행하기위한 가장 좋고 가장 "아이디 오마 틱한"방법 중 하나입니다.

X=57890000
Y=57890010
< infile.txt head -n "$Y" | tail -n +"$X"

코멘트에서 Gilles가 지적한 것처럼 더 빠른 방법은

< infile.txt tail -n +"$X" | head -n "$((Y - X))"

이것이 더 빠른 이유는 첫 번째 X-1 라인이 head | tail접근 방식에 비해 파이프를 통과 할 필요가 없기 때문 입니다.

말한 당신의 질문은 약간의 오도이며 아마도이 접근법에 대한 근거가없는 잘못된 것들을 설명 할 것입니다.

  • 당신은 당신이 계산해야 할 말 A, B, C, D당신이 볼 수 있지만, 파일의 라인 카운트가 필요하지 않습니다 및 최대 1 계산 쉘 어쨌든 당신을 위해 할 수있는 것이 필요하다.

  • 배관이 필요한 것보다 많은 라인을 읽을 것이라고 걱정합니다. 실제로 이것은 사실이 아닙니다 tail | head. 파일 I / O 측면에서 얻을 수있는만큼 효율적입니다. 먼저 필요한 최소 작업량을 고려하십시오 . 파일에서 X '행 을 찾으려면 파일 을 신성하게 할 수있는 방법이 없기 때문에 모든 바이트를 읽고 X 줄 바꿈 기호를 세면 중지하는 것이 일반적인 방법입니다. X 번째 줄 의 오프셋 . * X * 행에 도달하면 Y 행 에서 중지하여 인쇄하기 위해 모든 행을 읽어야합니다 . 따라서 Y 줄 보다 적은 수치로 판독하면 접근 할 수 없습니다 . 이제는 Yhead -n $Y 이상을 읽지 않습니다.라인 (가장 가까운 버퍼 장치로 반올림되지만 버퍼가 올바르게 사용되면 성능이 향상되므로 오버 헤드에 대해 걱정할 필요가 없습니다). 또한을 (를) tail더 이상 읽지 head않으므로 head | tail가능한 한 가장 적은 수의 행 을 읽습니다 (더 이상 무시하고 무시할 수있는 버퍼링). 파이프를 사용하지 않는 단일 공구 접근 방식의 유일한 효율성 이점은 더 적은 프로세스 (따라서 오버 헤드)입니다.


1
이전에 리디렉션이 먼저 진행되는 것을 본 적이 없습니다. 차갑습니다. 파이프 흐름이 더 깨끗해집니다.
clacke

14

가장 정통적인 방법 (그러나 Gilles가 언급 한 것처럼 가장 빠른 방법은 아님 )을 사용하는 것 sed입니다.

귀하의 경우 :

X=57890000
Y=57890010
sed -n -e "$X,$Y p" -e "$Y q" filename

-n옵션은 관련 행만 stdout으로 인쇄됨을 의미합니다.

마무리 라인 번호의 끝에 있는 p 는 주어진 범위에서 라인을 인쇄하는 것을 의미합니다. Q 스크립트의 두 번째 부분은 파일의 나머지 부분을 건너 뜀으로써 시간을 절약 할 수 있습니다.


1
나는 기대 sed하고 tail | head파에있을 것이지만, 그것은 tail | head훨씬 빠릅니다 ( 내 대답 참조 ).
Gilles

1
내가 읽은 무엇에서 나는 몰라 tail/ head파일의 한쪽 끝을 트리밍하기 때문에, 더 "정통"으로 간주되어 그들이 만든하는지 정확하게이다. 이러한 자료에서는 sed대체 작업이 필요할 때만 그림에 들어가는 것 같습니다. 복잡한 작업에 대한 구문이 AWK보다 훨씬 나쁘기 때문에 훨씬 복잡한 작업이 시작될 때 그림에서 빠르게 밀려납니다. .
underscore_d

7

첫 번째 줄 lStart에서 마지막 줄 까지 선택할 범위를 알고 있으면 다음 을 lEnd계산할 수 있습니다.

lCount="$((lEnd-lStart+1))"

총 줄 수를 알고 있다면 lAll파일 끝까지의 거리를 계산할 수도 있습니다.

toEnd="$((lAll-lStart+1))"

그러면 두 가지 모두를 알게됩니다.

"how far from the start"            ($lStart) and
"how far from the end of the file"  ($toEnd).

다음 중 가장 작은 것을 선택하십시오 tailnumber.

tailnumber="$toEnd"; (( toEnd > lStart )) && tailnumber="+$linestart"

일관되게 가장 빠른 실행 명령을 사용할 수 있습니다.

tail -n"${tailnumber}" ${thefile} | head -n${lCount}

를 선택하면 추가 더하기 ( "+") 부호 $linestart가 표시됩니다 .

유일한 경고는 우리가 총 줄 수를 필요로하며, 찾는 데 약간의 시간이 걸릴 수 있다는 것입니다.
평소와 같이 :

linesall="$(wc -l < "$thefile" )"

측정 된 시간은 다음과 같습니다.

lStart |500| lEnd |500| lCount |11|
real   user   sys    frac
0.002  0.000  0.000  0.00  | command == tail -n"+500" test.in | head -n1
0.002  0.000  0.000  0.00  | command == tail -n+500 test.in | head -n1
3.230  2.520  0.700  99.68 | command == tail -n99999501 test.in | head -n1
0.001  0.000  0.000  0.00  | command == head -n500 test.in | tail -n1
0.001  0.000  0.000  0.00  | command == sed -n -e "500,500p;500q" test.in
0.002  0.000  0.000  0.00  | command == awk 'NR<'500'{next}1;NR=='500'{exit}' test.in


lStart |50000000| lEnd |50000010| lCount |11|
real   user   sys    frac
0.977  0.644  0.328  99.50 | command == tail -n"+50000000" test.in | head -n11
1.069  0.756  0.308  99.58 | command == tail -n+50000000 test.in | head -n11
1.823  1.512  0.308  99.85 | command == tail -n50000001 test.in | head -n11
1.950  2.396  1.284  188.77| command == head -n50000010 test.in | tail -n11
5.477  5.116  0.348  99.76 | command == sed -n -e "50000000,50000010p;50000010q" test.in
10.124  9.669  0.448  99.92| command == awk 'NR<'50000000'{next}1;NR=='50000010'{exit}' test.in


lStart |99999000| lEnd |99999010| lCount |11|
real   user   sys    frac
0.001  0.000  0.000  0.00  | command == tail -n"1001" test.in | head -n11
1.960  1.292  0.660  99.61 | command == tail -n+99999000 test.in | head -n11
0.001  0.000  0.000  0.00  | command == tail -n1001 test.in | head -n11
4.043  4.704  2.704  183.25| command == head -n99999010 test.in | tail -n11
10.346  9.641  0.692  99.88| command == sed -n -e "99999000,99999010p;99999010q" test.in
21.653  20.873  0.744  99.83 | command == awk 'NR<'99999000'{next}1;NR=='99999010'{exit}' test.in

선택한 선이 시작 또는 끝 근처에 있으면 시간이 크게 변경됩니다. 파일의 한쪽에서 잘 작동하는 것으로 보이는 명령은 파일의 다른 쪽에서 매우 느릴 수 있습니다.


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
terdon

@BinaryZebra - 방법 더 나은.
mikeserv

0

나는 이것을 충분히 자주 하고이 스크립트를 썼다. 줄 번호를 찾을 필요가 없으며 스크립트가 모두 수행합니다.

#!/bin/bash

# $1: start time
# $2: end time
# $3: log file to read
# $4: output file

# i.e. log_slice.sh 18:33 19:40 /var/log/my.log /var/log/myslice.log

if [[ $# != 4 ]] ; then 
echo 'usage: log_slice.sh <start time> <end time> <log file> <output file>'
echo
exit;
fi

if [ ! -f $3 ] ; then
echo "'$3' doesn't seem to exit."
echo 'exiting.'
exit;
fi

sline=$(grep -n " ${1}" $3|head -1|cut -d: -f1)  #what line number is first occurrance of start time
eline=$(grep -n " ${2}" $3|head -1|cut -d: -f1)  #what line number is first occurrance of end time

linediff="$((eline-sline))"

tail -n+${sline} $3|head -n$linediff > $4

2
당신은 묻지 않은 질문에 대답하고 있습니다. 귀하의 답변은 10 % tail|head이며, 질문 및 기타 답변에서 광범위하게 논의되었으며 90 % 는 질문에 포함되지 않은 지정된 문자열 / 패턴이 나타나는 줄 번호를 결정합니다 . 추신 : 항상 쉘 매개 변수와 변수를 인용해야합니다. 예 : "$ 3"및 "$ 4"
G-Man
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.