특정 파일에서 n 번째 줄을 얻는 Bash 도구




리눅스 파일 라인 읽기 (16)

내 테스트에 따르면, 성능 및 가독성의 측면에서 내 추천은 다음과 같습니다.

tail -n+N | head -1

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

tail -n+Ntail -n+N 행부터 시작하여 모든 것을 출력하고, head -1 은 한 행 뒤에서 멈추게 할 것이다.

대체 head -N | tail -1 head -N | tail -1 은 아마도 약간 더 읽기 쉽습니다. 예를 들어, 다음과 같이 7 행을 인쇄합니다.

head -7 input.txt | tail -1

퍼포먼스와 관련해서는 더 작은 크기에서는 큰 차이가 없지만 tail | head 에서는 성능이 크게 향상 될 것입니다. 파일이 거대 해지면 (위)에서 tail | head .

최고 득표 sed 'NUMq;d' 는 알아두면 재미 있지만, 헤드 / 테일 솔루션보다 상자에서 꺼내는 사람이 더 쉽게 이해할 수 있으며 꼬리 / 머리보다 느린 것이라고 주장 할 것입니다.

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

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

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

결과는 다를 수 있지만 성능 head | tail head | tailtail | head 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

다음은 내 컴퓨터에서 실행 한 결과입니다 (ThinkPad X1 Carbon, SSD 및 16G 메모리 포함). 나는 최종 실행에서 모든 것이 디스크가 아닌 캐시에서 발생한다고 가정합니다.

*** 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

그 일을하는 "정식"방법이 있습니까? 나는 head -n | tail -1 head -n | tail -1 트릭을 않습니다,하지만 난 특별히 파일에서 라인 (또는 라인의 범위)를 추출하는 배쉬 도구가 궁금했습니다.

"정식"이란 주된 기능을 수행하는 프로그램을 의미합니다.


echo <filename> | head <n>

여기서 n은 인쇄 할 줄 번호입니다.


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

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

설정

행당 하나의 키 - 값 쌍이있는 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

50,000,000 행에 대한 기준선은 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

와, 모든 가능성!

이 시도:

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 는 아마 사용하기에 가장 가깝고 가장 간단합니다.



변수를 줄 번호로 사용하여 snd를 사용하여 n 번째 줄을 인쇄하려면,

a=4
sed -e $a'q:d' file

여기서 '-e'플래그는 실행할 명령에 스크립트를 추가하기위한 플래그입니다.


tailtail head 와 파이프는 거대한 파일에 대해서는 느려질 것입니다. 나는 이렇게 sed 제안 :

sed 'NUMq;d' file

여기서 NUM 은 인쇄 할 줄 번호입니다. 예를 들어 sed '10q;d' filefile 의 10 번째 줄을 인쇄 file .

설명:

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

d 는 인쇄하지 않고 줄을 지 웁니다. 마지막 줄에서는 q 가 종료 할 때 나머지 스크립트를 건너 뛸 수 있기 때문에 이것은 금지됩니다.

변수에 NUM 있으면 단일 대신 이중 따옴표를 사용하는 것이 좋습니다.

sed "${NUM}q;d" file

가능한 방법 중 하나 :

sed -n 'NUM{p;q}'

q 명령을 사용하지 않으면 파일이 크면 sed가 계속 작동하므로 계산 속도가 느려집니다.


많은 좋은 답변이 이미 있습니다. 나는 개인적으로 awk와 함께 간다. 편의상 bash를 사용한다면 ~/.bash_profile 아래를 추가하면됩니다. 그리고 다음에 로그인 할 때 (또는이 업데이트 이후에 .bash_profile을 소스로 사용하는 경우) 파일을 파이프 할 수있는 새롭고 멋진 "nth"기능을 사용할 수 있습니다.

이것을 실행하거나 ~ / .bash_profile (bash를 사용하는 경우)에 넣고 bash를 다시 엽니 다 (또는 source ~/.bach_profile 실행 source ~/.bach_profile )

# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }

그런 다음 그것을 사용하려면 간단하게 파이프를 통과 시키십시오. 예 :

$ yes line | cat -n | nth 5 5 line


다른 사람들이 언급 한 것을 사용하여, 나는 이것이 bash 쉘의 빠른 & 멋장이 기능이되기를 원했습니다.

파일 만들기 : ~/.functions

그것에 내용을 추가하십시오 :

getline() { line=$1 sed $line'q;d' $2 }

그런 다음 ~/.bash_profile 추가하십시오.

source ~/.functions

이제 새 bash 창을 열 때 함수를 다음과 같이 호출 할 수 있습니다.

getline 441 myfile.txt


sed print를 사용하고 종료 할 수도 있습니다 :

sed -n '10{p;q;}' file   # print line 10

위의 모든 대답이 질문에 직접 답합니다. 그러나 여기에는 덜 직접적인 해결책이지만 생각을 자극하기 위해 잠재적으로보다 중요한 아이디어가 있습니다.

줄 길이는 임의적이므로 n 번째 줄 앞에있는 파일의 모든 바이트를 읽을 필요 가 있습니다. 거대한 파일이 있거나이 작업을 여러 번 반복해야하는 경우이 프로세스에 많은 시간이 소요되는 경우 먼저 데이터를 다른 방식으로 저장해야하는지 여부에 대해 심각하게 생각해야합니다.

실제 해결책은 파일 시작과 같은 색인을 사용하여 행이 시작되는 위치를 표시하는 것입니다. 데이터베이스 형식을 사용하거나 파일 시작 부분에 테이블을 추가 할 수 있습니다. 또는 큰 텍스트 파일에 첨부 할 별도의 색인 파일을 작성하십시오.

예를 들어 개행 문자 위치 목록을 만들 수 있습니다.

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

파일의 적절한 지점으로 실제로 seek tail 로 읽습니다!

예를 들어 1000 줄을 얻으려면 :

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • awk는 "문자 인식"이지만 꼬리는 그렇지 않기 때문에 2 바이트 / 멀티 바이트 문자에서는 작동하지 않을 수 있습니다.
  • 나는 큰 파일에 대해 이것을 테스트하지 않았다.
  • 이 답변 도 참조하십시오.
  • 또는 작은 파일로 파일을 분할하십시오!

이 질문은 Bash, Bash (≥4) 방식으로 태그가 붙습니다 : mapfile-s (건너 뛰기) 및 -n (개수) 옵션과 함께 사용하십시오.

파일의 42 번째 줄을 가져와야 할 경우 :

mapfile -s 41 -n 1 ary < file

이 시점에서 첫 번째 41 줄 ( -s 41 )을 건너 뛰고 한 줄 ( -n 1 읽은 후 중지 한 file 줄 (줄 바꿈 줄 포함)을 포함하는 필드가있는 배열을 만들 것입니다. -n 1 ). 그래서 그것은 실제로 42 번째 줄입니다. 인쇄하려면 다음과 같이하십시오.

printf '%s' "${ary[0]}"

범위가 필요하다면 42-666 범위를 말하고, 직접 계산하고 싶지는 않다고합시다. 그리고 stdout에 출력하십시오 :

mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"

이 줄들을 처리해야한다면, 줄 바꿈 줄을 저장하는 것이 편리하지 않습니다. 이 경우 -t 옵션 (trim)을 사용하십시오 :

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"

당신이 할 수있는 기능을 당신을 위해 :

print_file_range() {
    # $1-$2 is the range of file $3 to be printed to stdout
    local ary
    mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3"
    printf '%s' "${ary[@]}"
}

외부 명령이 없으며 Bash 내장 명령 만!


대용량 파일을위한 가장 빠른 솔루션은 두 가지 거리가 제공된다면 항상 tail | head입니다.

  • 파일의 시작부터 시작 라인까지. 그것을 S 라고 부르 자.
  • 마지막 행에서부터 파일의 끝까지의 거리. 그것은 E

알려져있다. 그런 다음이를 사용할 수 있습니다.

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

방법은 필요한 라인 수에 불과합니다.

자세한 내용은 https://unix.stackexchange.com/a/216614/79743


CaffeineConnoisseur의 매우 유용한 벤치마킹 대답에 대한 후속 조치로서 ... 'mapfile'방법이 (테스트되지 않았으므로) 다른 사람과 얼마나 빨리 비교되었는지 궁금해서 빨리 속도 위반 비교를 시도했습니다. 나는 배쉬 4를 편리하게 사용한다. 사람들이 그 칭찬을 부르는 동안 내가 맨 위에있는 동안 맨 위로 대답에 대한 의견 중 하나에 언급 된 "꼬리 | 꼬리"방법 (머리 | 꼬리보다는 오히려)의 시험을 던졌습니다. 나는 거의 사용 된 testfile의 크기를 가지고 있지 않다; 짧은 시간에 발견 할 수있는 가장 좋은 것은 14M 가계도 파일 (공백으로 구분 된 긴 줄, 바로 아래 12000 줄)이었습니다.

짧은 버전 : mapfile은 cut 메소드보다 빠르지 만 다른 모든 것보다 느리다. 그래서 나는 그것을 불량배라고 부른다. 꼬리 | 머리, OTOH, 그것이 가장 빠를 수있는 것처럼 보입니다. 비록 파일 크기가이 차이가 sed에 비해 그다지 중요하지는 않습니다.

$ time head -11000 [filename] | tail -1
[output redacted]

real    0m0.117s

$ time cut -f11000 -d$'\n' [filename]
[output redacted]

real    0m1.081s

$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]

real    0m0.058s

$ time perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]

real    0m0.085s

$ time sed "11000q;d" [filename]
[output redacted]

real    0m0.031s

$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]

real    0m0.309s

$ time tail -n+11000 [filename] | head -n1
[output redacted]

real    0m0.028s

희망이 도움이!





sed