[bash] 파일에서 n 번째 줄을 가져 오는 배쉬 도구

그렇게하는 “정식적인”방법이 있습니까? 나는 사용하고있다head -n | tail -1 트릭을 수행하는 것을 했지만 파일에서 한 줄 또는 여러 줄을 추출하는 Bash 도구가 있는지 궁금합니다.

“정규”라는 말은 주요 기능을 수행하는 프로그램을 의미합니다.



답변

head파이프가 tail큰 파일의 경우 속도가 느립니다. 나는 이렇게 제안 할 것이다 sed:

sed 'NUMq;d' file

NUM인쇄하려는 줄 번호는 어디에 있습니까 ? 예를 들어, sed '10q;d' file10 번째 줄을 인쇄하려면file .

설명:

NUMq줄 번호가이면 즉시 종료됩니다 NUM.

d인쇄하는 대신 줄을 삭제합니다. 종료시 q스크립트의 나머지 부분을 건너 뛰기 때문에 마지막 행에서 금지됩니다 .

NUM변수 가 있는 경우 작은 따옴표 대신 큰 따옴표를 사용하려고합니다.

sed "${NUM}q;d" file


답변

sed -n '2p' < file.txt

두 번째 줄을 인쇄합니다

sed -n '2011p' < file.txt

2011 년 라인

sed -n '10,33p' < file.txt

10 행-33 행

sed -n '1p;3p' < file.txt

첫 번째와 세 번째 줄

등등…

sed로 줄을 추가하려면 다음을 확인하십시오.

sed : 특정 위치에 선을 삽입하십시오


답변

이 페이지에서 제안한 솔루션을 벤치마킹 할 수있는 독특한 상황이 있으므로이 답변을 각 솔루션에 대한 런타임이 포함 된 제안 솔루션의 통합으로 작성하고 있습니다.

설정

행당 하나의 키-값 쌍을 가진 3.261 기가 바이트 ASCII 텍스트 데이터 파일이 있습니다. 파일에는 총 3,339,550,320 행이 포함되어 있으며 Vim으로 이동하는 것을 포함하여 시도한 모든 편집기에서 열리지 않습니다. 발견 한 일부 값을 조사하려면 ~ 500,000,000 행 정도에서 시작해야합니다.

파일에 행이 너무 많기 때문에 :

  • 데이터에 유용한 작업을 수행하려면 행의 하위 집합 만 추출하면됩니다.
  • 내가 관심을 갖는 가치로 이어지는 모든 행을 읽는 데는 오랜 시간이 걸릴 것입니다.
  • 솔루션이 내가 관심있는 행을지나 읽고 나머지 파일을 계속 읽는 경우 거의 30 억 개의 관련 행을 읽는 데 시간을 낭비하고 필요한 것보다 6 배 더 오래 걸립니다.

가장 좋은 시나리오는 파일의 다른 행을 읽지 않고 파일에서 한 줄만 추출하는 솔루션이지만 Bash 에서이 작업을 수행하는 방법을 생각할 수 없습니다.

내 정신 건강을 위해 나는 내 자신의 문제에 필요한 500,000,000 줄 전체를 읽으려고하지 않을 것입니다. 대신 3,339,550,320에서 50,000,000 행을 추출하려고 시도합니다 (전체 파일을 읽는 데 필요한 시간보다 60 배 더 오래 걸립니다).

time내장 명령을 사용하여 각 명령을 벤치마킹 할 것입니다.

베이스 라인

먼저 head tail솔루션이 어떻게 작동하는지 봅시다 :

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

5 천만 행의 기준선은 00 : 01 : 15.321입니다. 5 억 행에 대해 직진하면 아마 ~ 12.5 분이됩니다.

절단

나는 이것에 대해 의심 스럽다. 그러나 그것은 가치가있다.

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

이것은 실행하는 데 00 : 05 : 12.156이 걸렸으며 이는 기준선보다 훨씬 느립니다! 중지하기 전에 전체 파일을 읽거나 5 천만 줄까지 읽었는지 확실하지 않지만 이것이 문제에 대한 실용적인 해결책처럼 보이지는 않습니다.

AWK

exit전체 파일이 실행될 때까지 기다리지 않기 때문에 솔루션 만 실행했습니다 .

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

이 코드는 00 : 01 : 16.583에서 실행되었는데, 이는 ~ 1 초 느리지 만 여전히 기준선의 개선은 아닙니다. 종료 명령이 제외 된 경우이 속도로 전체 파일을 읽는 데 약 ~ 76 분이 걸렸을 것입니다!

기존 Perl 솔루션도 실행했습니다.

$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

이 코드는 00 : 01 : 13.146에서 실행되었으며 기준보다 2 초 빠릅니다. 전체 500,000,000에서 실행하면 ~ 12 분이 걸릴 것입니다.

sed

보드의 최고 답변은 다음과 같습니다.

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

이 코드는 00 : 01 : 12.705에서 실행되었는데, 이는 기준선보다 3 초 빠르며 Perl보다 ~ 0.4 초 빠릅니다. 전체 500,000,000 행에서 실행하면 ~ 12 분이 걸렸을 것입니다.

맵 파일

bash 3.1이있어 맵 파일 솔루션을 테스트 할 수 없습니다.

결론

대부분 head tail솔루션 을 개선하기가 어려운 것처럼 보입니다 . 기껏해야sed 솔루션은 ~ 3 %의 효율성 향상을 제공합니다.

(공식으로 계산 한 백분율 % = (runtime/baseline - 1) * 100)

50,000,000 행

  1. 00 : 01 : 12.705 (-00 : 00 : 02.616 = -3.47 %) sed
  2. 00 : 01 : 13.146 (-00 : 00 : 02.175 = -2.89 %) perl
  3. 00 : 01 : 15.321 (+00 : 00 : 00.000 = + 0.00 %) head|tail
  4. 00 : 01 : 16.583 (+00 : 00 : 01.262 = + 1.68 %) awk
  5. 00 : 05 : 12.156 (+00 : 03 : 56.835 = + 314.43 %) cut

행 500,000,000

  1. 00 : 12 : 07.050 (-00 : 00 : 26.160) sed
  2. 00 : 12 : 11.460 (-00 : 00 : 21.750) perl
  3. 00 : 12 : 33.210 (+00 : 00 : 00.000) head|tail
  4. 00 : 12 : 45.830 (+00 : 00 : 12.620) awk
  5. 00 : 52 : 01.560 (+00 : 40 : 31.650) cut

3,338,559,320 열

  1. 01 : 20 : 54.599 (-00 : 03 : 05.327) sed
  2. 01 : 21 : 24.045 (-00 : 02 : 25.227) perl
  3. 01 : 23 : 49.273 (+00 : 00 : 00.000) head|tail
  4. 01 : 25 : 13.548 (+00 : 02 : 35.735) awk
  5. 05 : 47 : 23.026 (+04 : 24 : 26.246) cut

답변

함께 awk꽤 빠른입니다 :

awk 'NR == num_line' file

이것이 참이면 기본 동작은 awk다음과 같습니다 {print $0}..


대체 버전

파일이 크면 exit필요한 줄을 읽은 후 더 좋습니다 . 이렇게하면 CPU 시간을 절약 할 수 있습니다. 답변이 끝나면 시간 비교를보십시오 .

awk 'NR == num_line {print; exit}' file

bash 변수에서 줄 번호를 지정하려면 다음을 사용할 수 있습니다.

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent

exit특히 줄이 파일의 첫 부분에있는 경우을 사용하여 시간이 얼마나 절약 되는지 확인하십시오.

# Let's create a 10M lines file
for ((i=0; i<100000; i++)); do echo "bla bla"; done > 100Klines
for ((i=0; i<100; i++)); do cat 100Klines; done > 10Mlines

$ time awk 'NR == 1234567 {print}' 10Mlines
bla bla

real    0m1.303s
user    0m1.246s
sys 0m0.042s
$ time awk 'NR == 1234567 {print; exit}' 10Mlines
bla bla

real    0m0.198s
user    0m0.178s
sys 0m0.013s

따라서 차이는 0.198 초 대 1.303 초로 약 6 배 더 빠릅니다.


답변

내 테스트에 따르면 성능 및 가독성 측면에서 권장 사항은 다음과 같습니다.

tail -n+N | head -1

N원하는 줄 번호입니다. 예를 들어 tail -n+7 input.txt | head -1파일의 7 번째 줄을 인쇄합니다.

tail -n+Nline N에서 시작하는 모든 것을 인쇄 하고 head -1한 줄 후에 중지합니다.


대안 head -N | tail -1은 약간 더 읽기 쉽습니다. 예를 들어, 이것은 일곱 번째 줄을 인쇄합니다 :

head -7 input.txt | tail -1

성능면에서 작은 크기의 경우 큰 차이는 없지만 tail | head파일이 커지면 (위에서) 성능이 뛰어 납니다.

최고 투표권 sed 'NUMq;d'은 알고있는 것이 흥미롭지 만 헤드 / 테일 솔루션보다 더 적은 수의 사람들이 이해할 수 있으며 테일 / 헤드보다 느리다고 주장합니다.

테스트에서 테일 / 헤드 버전이 모두 성능이 우수했습니다. sed 'NUMq;d' 일관되게 되었습니다. 그것은 게시 된 다른 벤치 마크와 일치합니다. 꼬리 / 머리가 실제로 나쁜 경우를 찾기는 어렵습니다. 현대 유닉스 시스템에서 크게 최적화 될 것으로 예상되는 작업이기 때문에 놀라운 일이 아닙니다.

성능 차이에 대한 아이디어를 얻으려면 거대한 파일 (9.3G)에 대해 얻는 숫자입니다.

  • tail -n+N | head -1: 3.7 초
  • head -N | tail -1: 4.6 초
  • sed Nq;d: 18.8 초

결과는 다를 수 있지만, 성능이 수 head | tailtail | head, 일반적으로 작은 입력에 대한 비교, 그리고 sed중요한 요소 (5 배 주위 정도)에 의해 항상 느립니다.

내 벤치 마크를 재현하기 위해 다음을 시도 할 수 있지만 현재 작업 디렉토리에 9.3G 파일이 생성된다는 경고가 표시됩니다.

#!/bin/bash
readonly file=tmp-input.txt
readonly size=1000000000
readonly pos=500000000
readonly retries=3

seq 1 $size > $file
echo "*** head -N | tail -1 ***"
for i in $(seq 1 $retries) ; do
    time head "-$pos" $file | tail -1
done
echo "-------------------------"
echo
echo "*** tail -n+N | head -1 ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time tail -n+$pos $file | head -1
done
echo "-------------------------"
echo
echo "*** sed Nq;d ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time sed $pos'q;d' $file
done
/bin/rm $file

다음은 내 컴퓨터에서 실행 한 결과입니다 (SSD 및 16G 메모리가있는 ThinkPad X1 Carbon). 나는 마지막 실행에서 모든 것이 디스크가 아닌 캐시에서 온다고 가정합니다.

*** head -N | tail -1 ***
500000000

real    0m9,800s
user    0m7,328s
sys     0m4,081s
500000000

real    0m4,231s
user    0m5,415s
sys     0m2,789s
500000000

real    0m4,636s
user    0m5,935s
sys     0m2,684s
-------------------------

*** tail -n+N | head -1 ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt
500000000

real    0m6,452s
user    0m3,367s
sys     0m1,498s
500000000

real    0m3,890s
user    0m2,921s
sys     0m0,952s
500000000

real    0m3,763s
user    0m3,004s
sys     0m0,760s
-------------------------

*** sed Nq;d ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt
500000000

real    0m23,675s
user    0m21,557s
sys     0m1,523s
500000000

real    0m20,328s
user    0m18,971s
sys     0m1,308s
500000000

real    0m19,835s
user    0m18,830s
sys     0m1,004s


답변

와우, 모든 가능성!

이 시도:

sed -n "${lineNum}p" $file

또는 Awk 버전에 따라 다음 중 하나입니다.

awk  -vlineNum=$lineNum 'NR == lineNum {print $0}' $file
awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file
awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file

( 당신은 시도해야 할 수도 있습니다nawk 또는 gawk명령 ).

특정 라인 만 인쇄하는 도구가 있습니까? 표준 도구 중 하나가 아닙니다. 그러나 sed가장 사용하기 가장 쉬운 방법 일 것입니다.


답변

# print line number 52
sed '52!d' file

sed에 유용한 한 줄 스크립트