cat
큰 텍스트 파일(>2GB)이 있고 줄만 쓰고 싶다고 가정해 보겠습니다 X
( Y
예: 57890000 ~ 57890010).
내가 이해하는 한 파이프를 연결하거나 그 반대로 할 수 head
있습니다 tail
.
head -A /path/to/file | tail -B
또는 대안적으로
tail -C /path/to/file | head -D
그 중 A
, B
, C
는 D
파일의 행 수를 기준으로 계산할 수 있으며, X
는 Y
.
그러나 이 접근 방식에는 두 가지 문제가 있습니다.
A
,B
를 계산해야 합니다C
.D
- 명령은
pipe
서로 전달될 수 있습니다.더 많은 것이 있습니다내가 읽고 싶은 줄보다 (예: 큰 파일의 중간에 몇 줄만 읽는 경우)
쉘에서 내가 원하는 라인만 처리하고 출력할 수 있는 방법이 있나요? (동시에 AND만 가능합니다 X
.) Y
?
답변1
해결책을 제안하고 싶지만 sed
완전성을 위해
awk 'NR >= 57890000 && NR <= 57890010' /path/to/file
마지막 줄 뒤에서 잘라내려면:
awk 'NR < 57890000 { next } { print } NR == 57890010 { exit }' /path/to/file
속도 테스트(여기서는 macOS, 다른 시스템에서는 YMMV):
- 100,000,000 라인 파일 생성
seq 100000000 > test.in
- 읽은 줄 수 50,000,000-50,000,010
- 특별한 순서 없이 테스트
real
bash
내장된 시간 보고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
본질적으로 동일해 보입니다 .
답변2
X부터 Y까지의 행(1부터 시작하는 번호)을 포함하려면 다음을 사용하십시오.
tail -n "+$X" /path/to/file | head -n "$((Y-X+1))"
tail
첫 번째 X-1 줄은 읽고 삭제되며(이 문제는 해결되지 않음) 다음 줄은 읽고 인쇄됩니다. head
요청된 행 수를 읽고 인쇄한 다음 종료합니다. 출구 head
에서tail
신호 파이프라인버퍼 크기(보통 몇 킬로바이트)보다 입력 파일에서 더 많은 라인을 읽지 않도록 신호를 보내고 죽습니다.
또는고리키푸르sed를 사용하는 것이 좋습니다:
sed -n -e "$X,$Y p" -e "$Y q" /path/to/file
그러나 sed 솔루션은 상당히 느립니다(적어도 GNU 유틸리티 및 Busybox 유틸리티의 경우 파이프가 느리고 sed가 빠른 운영 체제에서 대부분의 파일을 추출하는 경우 sed가 더 경쟁력이 있을 수 있습니다). 다음은 Linux에서의 빠른 벤치마크입니다. 데이터는 에서 생성되었으며 seq 100000000 >/tmp/a
환경은 Linux/amd64, /tmp
환경은 tmpfs이고 머신은 유휴 상태이며 스와핑되지 않습니다.
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
답변3
이 head | tail
방법은 이를 달성하는 가장 좋고 가장 "관용적인" 방법 중 하나입니다.
X=57890000
Y=57890010
< infile.txt head -n "$Y" | tail -n +"$X"
Giles가 의견에서 지적했듯이 더 빠른 방법은
< infile.txt tail -n +"$X" | head -n "$((Y - X))"
빠른 이유는 첫째X-1방법과 달리 라인은 파이프를 통과할 필요가 없습니다 head | tail
.
귀하의 질문 문구는 약간 오해의 소지가 있으며 이 접근 방식에 대한 근거 없는 불안감을 설명할 수 있습니다.
A
,B
, 를 계산해야 한다고 말씀하셨는데C
,D
보시다시피 파일의 줄 수는 필요하지 않으며 최대 1번의 계산이 필요하며 어쨌든 쉘이 이를 수행할 수 있습니다.파이프가 너무 많은 줄을 읽을까 봐 걱정됩니다. 사실 이는 사실이 아닙니다.
tail | head
파일 I/O 측면에서 얻을 수 있는 만큼 효율적입니다. 먼저 필요한 최소한의 노력을 고려하십시오.엑스파일의 3번째 줄, 유일한 일반적인 방법은 각 바이트를 읽고 계산할 때 중지하는 것입니다.엑스파일의 오프셋을 예측할 수 없기 때문에 개행 문자엑스'열. *X* 행에 도달하면 인쇄하기 전에 모든 행을 읽어야 합니다.예'번째 줄. 그러므로 책을 적게 읽는 것을 피할 방법은 없습니다.예철사. 이제head -n $Y
더 이상 읽지 마세요.예행(가장 가까운 버퍼 단위로 반올림되지만 버퍼는 올바르게 사용하면 성능을 향상시킬 수 있으므로 오버헤드에 대해 걱정할 필요가 없습니다). 또한tail
그 이상은 읽혀지지 않으므로 가능한 한 적은 수의 줄을 읽는head
것으로 표시했습니다head | tail
(다시 말하지만 무시할 만한 무시할 만한 버퍼링도 포함). 파이프가 없는 단일 도구 접근 방식의 유일한 효율성 이점은 프로세스 수가 적다는 것입니다(따라서 오버헤드도 적습니다).
답변4
선택할 범위를 알고 있으면 첫 번째 행부터 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
선택한 경로가 시작점이나 끝점에 가까울 경우 시간이 크게 바뀔 수 있다는 점에 유의하세요. 파일의 한쪽에서는 잘 실행되는 것처럼 보이는 명령이 파일의 다른 쪽에서는 매우 느릴 수도 있습니다.