[bash] 파일에서 n 번째 줄을 가져 오는 배쉬 도구
그렇게하는 “정식적인”방법이 있습니까? 나는 사용하고있다head -n | tail -1
트릭을 수행하는 것을 했지만 파일에서 한 줄 또는 여러 줄을 추출하는 Bash 도구가 있는지 궁금합니다.
“정규”라는 말은 주요 기능을 수행하는 프로그램을 의미합니다.
답변
head
파이프가 tail
큰 파일의 경우 속도가 느립니다. 나는 이렇게 제안 할 것이다 sed
:
sed 'NUMq;d' file
NUM
인쇄하려는 줄 번호는 어디에 있습니까 ? 예를 들어, sed '10q;d' file
10 번째 줄을 인쇄하려면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로 줄을 추가하려면 다음을 확인하십시오.
답변
이 페이지에서 제안한 솔루션을 벤치마킹 할 수있는 독특한 상황이 있으므로이 답변을 각 솔루션에 대한 런타임이 포함 된 제안 솔루션의 통합으로 작성하고 있습니다.
설정
행당 하나의 키-값 쌍을 가진 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 행
- 00 : 01 : 12.705 (-00 : 00 : 02.616 = -3.47 %)
sed
- 00 : 01 : 13.146 (-00 : 00 : 02.175 = -2.89 %)
perl
- 00 : 01 : 15.321 (+00 : 00 : 00.000 = + 0.00 %)
head|tail
- 00 : 01 : 16.583 (+00 : 00 : 01.262 = + 1.68 %)
awk
- 00 : 05 : 12.156 (+00 : 03 : 56.835 = + 314.43 %)
cut
행 500,000,000
- 00 : 12 : 07.050 (-00 : 00 : 26.160)
sed
- 00 : 12 : 11.460 (-00 : 00 : 21.750)
perl
- 00 : 12 : 33.210 (+00 : 00 : 00.000)
head|tail
- 00 : 12 : 45.830 (+00 : 00 : 12.620)
awk
- 00 : 52 : 01.560 (+00 : 40 : 31.650)
cut
3,338,559,320 열
- 01 : 20 : 54.599 (-00 : 03 : 05.327)
sed
- 01 : 21 : 24.045 (-00 : 02 : 25.227)
perl
- 01 : 23 : 49.273 (+00 : 00 : 00.000)
head|tail
- 01 : 25 : 13.548 (+00 : 02 : 35.735)
awk
- 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+N
line 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 | tail
와 tail | 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
가장 사용하기 가장 쉬운 방법 일 것입니다.