bash - 특정 - 리눅스 파일 라인 읽기




파일에서 n 번째 줄을 얻는 Bash 도구 (13)

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

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


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

희망이 도움이!


\ n (일반적으로 새 줄)으로 구분 된 여러 줄이있는 경우. 'cut'도 사용할 수 있습니다.

echo "$data" | cut -f2 -d$'\n'

파일에서 두 번째 줄을 가져옵니다. -f3 는 세 번째 줄을 제공합니다.


가능한 방법 중 하나 :

sed -n 'NUM{p;q}'

q 명령을 사용하지 않으면 파일이 크면 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

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

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

파일 만들기 : ~/.functions

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

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

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

source ~/.functions

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

getline 441 myfile.txt


대용량 파일을위한 가장 빠른 솔루션은 두 가지 거리가 제공된다면 항상 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


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

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

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


와, 모든 가능성!

이 시도:

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


이 경우 Perl을 사용할 수도 있습니다.

perl -wnl -e '$.== NUM && print && exit;' some.file

이 질문은 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 내장 명령 만!


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 '2p' < file.txt

2 라인 인쇄

sed -n '2011p' < file.txt

2011 라인

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

10 호선에서 33 호선까지

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

1 및 3 번째 줄

등등...

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

sed : 특정 위치에 줄을 삽입한다.





sed