Công cụ Bash để có được dòng thứ n từ một tệp


605

Có một cách "kinh điển" để làm điều đó? Tôi đã sử dụng head -n | tail -1thủ thuật này, nhưng tôi đã tự hỏi liệu có một công cụ Bash nào đặc biệt trích xuất một dòng (hoặc một phạm vi dòng) từ một tệp không.

Theo "canonical", ý tôi là một chương trình có chức năng chính là làm điều đó.


10
"Cách Unix" là xâu chuỗi các công cụ làm tốt công việc tương ứng của chúng. Vì vậy, tôi nghĩ rằng bạn đã tìm thấy một phương pháp rất phù hợp. Các phương pháp khác bao gồm awksedvà tôi chắc chắn rằng ai đó cũng có thể đưa ra một lớp lót Perl hoặc tương tự;)
0xC0000022L

3
Lệnh double cho thấy head | tailgiải pháp là tối ưu phụ. Các giải pháp khác gần như tối ưu hơn đã được đề xuất.
Jonathan Leffler

Bạn đã chạy bất kỳ điểm chuẩn nào về giải pháp nào là nhanh nhất cho một trường hợp trung bình chưa?
Marcin

5
Điểm chuẩn (cho một phạm vi) ở dòng mèo X đến dòng Y trên một tệp khổng lồ trên Unix & Linux . (cc @Marcin, trong trường hợp bạn vẫn đang tự hỏi sau hai + năm)
Kevin

6
Các head | tailgiải pháp không làm việc, nếu bạn truy vấn một dòng mà không tồn tại trong đầu vào: nó sẽ in dòng cuối cùng.
jarno

Câu trả lời:


802

headvà ống với tailsẽ chậm cho một tập tin lớn. Tôi muốn đề nghị sednhư thế này:

sed 'NUMq;d' file

Trong trường hợp NUMlà số dòng bạn muốn in; vì vậy, ví dụ, sed '10q;d' fileđể in dòng thứ 10 của file.

Giải trình:

NUMqsẽ thoát ngay lập tức khi số dòng là NUM.

dsẽ xóa dòng thay vì in nó; điều này bị ức chế ở dòng cuối cùng vì qcác phần còn lại của tập lệnh bị bỏ qua khi thoát.

Nếu bạn có NUMmột biến, bạn sẽ muốn sử dụng dấu ngoặc kép thay vì dấu ngoặc đơn:

sed "${NUM}q;d" file

44
Đối với những người thắc mắc, giải pháp này dường như nhanh hơn khoảng 6 đến 9 lần so với sed -n 'NUMp'sed 'NUM!d'các giải pháp được đề xuất dưới đây.
Skippy le Grand Gourou

75
Tôi nghĩ tail -n+NUM file | head -n1có khả năng là nhanh hoặc nhanh hơn. Ít nhất, nó đã nhanh hơn (đáng kể) trên hệ thống của tôi khi tôi dùng thử với NUM là 250000 trên một tệp có nửa triệu dòng. YMMV, nhưng tôi không thực sự hiểu tại sao nó lại như vậy.
rici

2
@rici (sửa đổi nhận xét trước đó) Trên Linux (Ubuntu 12.04, Fedora 20), sử dụng catthực sự nhanh hơn ( nhanh gần gấp đôi), nhưng chỉ khi tệp chưa được lưu vào bộ đệm . Khi tệp được lưu vào bộ đệm , việc sử dụng trực tiếp đối số tên tệp sẽ nhanh hơn (nhanh hơn khoảng 1/3), trong khi cathiệu suất vẫn giữ nguyên. Thật kỳ lạ, trên OS X 10.9.3 dường như không có sự khác biệt nào: cat/ không cat, tệp có được lưu trong bộ nhớ cache hay không. @anubhava: niềm vui của tôi.
mkuity0

2
@SkippyleGrandGourou: Với tính chất cụ thể của việc tối ưu hóa này , ngay cả phạm vi số của bạn cũng vô nghĩa như một tuyên bố chung . Điểm chung duy nhất là: (a) tối ưu hóa này có thể được áp dụng một cách an toàn cho tất cả các đầu vào, (b) các hiệu ứng sẽ dao động từ không đến kịch tính , tùy thuộc vào chỉ số của dòng tìm kiếm liên quan đến số lượng dòng tổng thể.
mkuity0

17
sed 'NUMqsẽ xuất NUMcác tập tin đầu tiên và ;dsẽ xóa tất cả trừ dòng cuối cùng.
anubhava

304
sed -n '2p' < file.txt

sẽ in dòng thứ 2

sed -n '2011p' < file.txt

Dòng thứ 2011

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

dòng 10 lên đến dòng 33

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

Dòng thứ 1 và thứ 3

và như thế...

Để thêm dòng với sed, bạn có thể kiểm tra điều này:

sed: chèn một dòng ở một vị trí nhất định


6
@RafaelBarbosa <trong trường hợp này là không cần thiết. Đơn giản, đó là sở thích của tôi khi sử dụng các chuyển hướng, bởi vì tôi thường sử dụng các chuyển hướng như sed -n '100p' < <(some_command)- vì vậy, cú pháp phổ quát :). Nó KHÔNG hiệu quả, bởi vì việc chuyển hướng được thực hiện bằng shell khi tự rót, vì vậy ... nó chỉ là một sở thích ... (và vâng, nó dài hơn một ký tự) :)
jm666

1
@ jm666 Trên thực tế, nó dài hơn 2 ký tự vì bạn thường đặt '<' cũng như thêm một khoảng trắng '' sau khi được đặt ở một khoảng trống nếu bạn không sử dụng <:)
rasen58

2
@ rasen58 không gian cũng là một nhân vật? :) / okay, đùa thôi - bạn nói đúng / :)
jm666

1
@duhaime tất nhiên, nếu ai đó cần làm tối ưu hóa. Nhưng IMHO cho các vấn đề "phổ biến" thì vẫn ổn và sự khác biệt là không đáng chú ý. Ngoài ra, head/ tailkhông giải quyết được sed -n '1p;3p'kịch bản - hay còn in thêm các hàng không liền kề ...
jm666

1
@duhaime tất nhiên - ghi chú là chính xác và cần thiết. :)
jm666

93

Tôi có một tình huống duy nhất là tôi có thể điểm chuẩn các giải pháp được đề xuất trên trang này và vì vậy tôi đang viết câu trả lời này dưới dạng hợp nhất các giải pháp được đề xuất với thời gian chạy bao gồm cho mỗi giải pháp.

Thiết lập

Tôi có tệp dữ liệu văn bản ASCII 3.261 gigabyte với một cặp khóa-giá trị mỗi hàng. Tổng cộng có tổng số 3.339.550.320 hàng và bất chấp mở trong bất kỳ trình chỉnh sửa nào tôi đã thử, bao gồm cả Vim của tôi. Tôi cần phải đặt tập tin này để điều tra một số giá trị mà tôi đã phát hiện ra chỉ bắt đầu quanh hàng ~ 500.000.000.

Bởi vì tệp có rất nhiều hàng:

  • Tôi chỉ cần trích xuất một tập hợp con của các hàng để làm bất cứ điều gì hữu ích với dữ liệu.
  • Đọc qua từng hàng dẫn đến các giá trị tôi quan tâm sẽ mất nhiều thời gian.
  • Nếu giải pháp đọc qua các hàng tôi quan tâm và tiếp tục đọc phần còn lại của tệp, nó sẽ lãng phí thời gian để đọc gần 3 tỷ hàng không liên quan và mất nhiều thời gian hơn 6 lần so với cần thiết.

Kịch bản trường hợp tốt nhất của tôi là một giải pháp chỉ trích xuất một dòng duy nhất từ ​​tệp mà không đọc bất kỳ hàng nào khác trong tệp, nhưng tôi không thể nghĩ về cách tôi sẽ thực hiện điều này trong Bash.

Vì mục đích của sự tỉnh táo của tôi, tôi sẽ không cố gắng đọc toàn bộ 500.000.000 dòng tôi cần cho vấn đề của riêng tôi. Thay vào đó, tôi sẽ cố gắng trích xuất hàng 50.000.000 trong số 3.339.550.320 (có nghĩa là đọc toàn bộ tệp sẽ mất 60 lần lâu hơn mức cần thiết).

Tôi sẽ sử dụng tích timehợp sẵn để đánh giá từng lệnh.

Đường cơ sở

Trước tiên hãy xem cách head tailgiải quyết:

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

real    1m15.321s

Đường cơ sở cho hàng 50 triệu là 00: 01: 15.321, nếu tôi đi thẳng cho hàng 500 triệu thì có thể là ~ 12,5 phút.

cắt

Tôi không biết điều này, nhưng nó đáng để thử:

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

real    5m12.156s

Cái này mất 00: 05: 12.156 để chạy, chậm hơn nhiều so với đường cơ sở! Tôi không chắc liệu nó có đọc qua toàn bộ tệp hay chỉ lên tới 50 triệu trước khi dừng, nhưng bất kể điều này có vẻ không phải là một giải pháp khả thi cho vấn đề.

AWK

Tôi chỉ chạy giải pháp với exitvì tôi sẽ không đợi tệp đầy đủ chạy:

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

real    1m16.583s

Mã này chạy trong 00: 01: 16.583, tốc độ chỉ chậm hơn ~ 1 giây, nhưng vẫn không phải là một cải tiến trên đường cơ sở. Với tốc độ này nếu lệnh thoát đã bị loại trừ, có lẽ sẽ mất khoảng 76 phút để đọc toàn bộ tệp!

Perl

Tôi cũng đã chạy giải pháp Perl hiện có:

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

real    1m13.146s

Mã này chạy trong 00: 01: 13.146, nhanh hơn ~ 2 giây so với đường cơ sở. Nếu tôi chạy nó với đầy đủ 500.000.000, có thể sẽ mất ~ 12 phút.

quyến rũ

Câu trả lời hàng đầu trên bảng, đây là kết quả của tôi:

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

real    1m12.705s

Mã này chạy trong 00: 01: 12.705, nhanh hơn 3 giây so với đường cơ sở và nhanh hơn ~ 0,4 giây so với Perl. Nếu tôi chạy nó trên toàn bộ 500.000.000 hàng, có thể mất khoảng 12 phút.

mapfile

Tôi có bash 3.1 và do đó không thể kiểm tra giải pháp mapfile.

Phần kết luận

Có vẻ như, đối với hầu hết các phần, thật khó để cải thiện head tailgiải pháp. Tốt nhất là sedgiải pháp cung cấp hiệu quả tăng ~ 3%.

(tỷ lệ phần trăm được tính theo công thức % = (runtime/baseline - 1) * 100)

Hàng 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 (GIÁ: 00: 00.000 = + 0,00%) head|tail
  4. 00: 01: 16.583 (GIÁ: 00: 01.262 = + 1.68%) awk
  5. 00: 05: 12.156 (GIÁ: 03: 56.835 = + 314,43%) cut

Hàng 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 (GIÁ: 00: 00.000) head|tail
  4. 00: 12: 45.830 (GIÁ: 00: 12.620) awk
  5. 00: 52: 01.560 (GIÁ: 40: 31.650) cut

Hàng 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 (GIÁ: 00: 00.000) head|tail
  4. 01: 25: 13,548 (GIÁ: 02: 35.735) awk
  5. 05: 47: 23.026 (+04: 24: 26.246) cut

4
Tôi tự hỏi chỉ cần kéo dài toàn bộ tệp vào / dev / null sẽ mất bao lâu. (Điều gì sẽ xảy ra nếu đây chỉ là một điểm chuẩn của đĩa cứng?)
sanmai

Tôi cảm thấy một sự thôi thúc gian tà cúi đầu trước quyền sở hữu của bạn đối với một từ điển tệp văn bản 3+ gig. Dù lý do là gì đi nữa, điều này rất phù hợp với kết cấu :)
Stablesog

51

Với awknó là khá nhanh:

awk 'NR == num_line' file

Khi điều này là đúng, hành vi mặc định của awkđược thực hiện : {print $0}.


Phiên bản thay thế

Nếu tập tin của bạn rất lớn, bạn sẽ tốt hơn exitsau khi đọc dòng yêu cầu. Bằng cách này bạn tiết kiệm thời gian CPU Xem so sánh thời gian ở cuối câu trả lời .

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

Nếu bạn muốn cung cấp số dòng từ biến bash, bạn có thể sử dụng:

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

Xem bao nhiêu thời gian được tiết kiệm bằng cách sử dụng exit, đặc biệt nếu dòng xảy ra trong phần đầu tiên của tệp:

# 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

Vì vậy, sự khác biệt là 0,198 so với 1,303, nhanh hơn khoảng 6 lần.


Phương pháp này sẽ luôn chậm hơn vì awk cố gắng thực hiện chia tách trường. Chi phí phân chia trường có thể giảm điawk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
kutilour

Sức mạnh thực sự của awk trong phương pháp này được lấy ra khi bạn muốn dòng n1 concatenate của file1, n2 của file2, n3 hoặc file3 ... awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3. Với GNU awk, điều này có thể được tăng tốc bằng cách sử dụng awk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3.
kocateour

@kvantour thực sự, nextfile của GNU awk rất tuyệt cho những thứ như vậy. Làm thế nào FS=RSđể tránh chia tách trường?
fedorqui 'SO ngừng gây hại'

1
FS=RSkhông tránh việc chia tách trường, nhưng nó chỉ phân tích cú pháp $ 0 và chỉ gán một trường vì không có RStrong$0
kocateour

@kvantour Tôi đã thực hiện một số thử nghiệm với FS=RSvà không thấy sự khác biệt về thời gian. Điều gì về tôi hỏi một câu hỏi về nó để bạn có thể mở rộng? Cảm ơn!
fedorqui 'SO ngừng gây hại'

29

Theo thử nghiệm của tôi, về hiệu suất và khả năng đọc, khuyến nghị của tôi là:

tail -n+N | head -1

Nlà số dòng mà bạn muốn. Ví dụ, tail -n+7 input.txt | head -1sẽ in dòng thứ 7 của tệp.

tail -n+Nsẽ in mọi thứ bắt đầu từ dòng Nhead -1sẽ làm cho nó dừng lại sau một dòng.


Sự thay thế head -N | tail -1có lẽ dễ đọc hơn một chút. Ví dụ: điều này sẽ in dòng thứ 7:

head -7 input.txt | tail -1

Khi nói đến hiệu suất, không có nhiều sự khác biệt đối với kích thước nhỏ hơn, nhưng nó sẽ vượt trội hơn tail | head(từ phía trên) khi các tệp trở nên khổng lồ.

Được bình chọn hàng đầu sed 'NUMq;d'là thú vị để biết, nhưng tôi sẽ lập luận rằng nó sẽ được hiểu bởi ít người hơn so với giải pháp đầu / đuôi và nó cũng chậm hơn đuôi / đầu.

Trong các thử nghiệm của tôi, cả hai phiên bản đuôi / đầu đều vượt trội hơn hẳn sed 'NUMq;d'. Điều đó phù hợp với các điểm chuẩn khác đã được đăng. Thật khó để tìm thấy một trường hợp mà đuôi / đầu thực sự xấu. Nó cũng không đáng ngạc nhiên, vì đây là những hoạt động mà bạn mong đợi sẽ được tối ưu hóa mạnh mẽ trong một hệ thống Unix hiện đại.

Để có ý tưởng về sự khác biệt về hiệu suất, đây là những con số tôi nhận được cho một tệp khổng lồ (9.3G):

  • tail -n+N | head -1: 3,7 giây
  • head -N | tail -1: 4,6 giây
  • sed Nq;d: 18,8 giây

Kết quả có thể khác nhau, nhưng hiệu suất head | tailtail | headnói chung, có thể so sánh với các đầu vào nhỏ hơn và sedluôn chậm hơn bởi một yếu tố quan trọng (khoảng 5x hoặc hơn).

Để tái tạo điểm chuẩn của tôi, bạn có thể thử các cách sau, nhưng được cảnh báo rằng nó sẽ tạo tệp 9.3G trong thư mục làm việc hiện tại:

#!/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

Đây là đầu ra của một lần chạy trên máy của tôi (ThinkPad X1 Carbon với ổ SSD và bộ nhớ 16G). Tôi giả sử trong lần chạy cuối cùng, mọi thứ sẽ đến từ bộ đệm, không phải từ đĩa:

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

1
Là hiệu suất khác nhau giữa head | tailvs tail | head? Hoặc nó phụ thuộc vào dòng nào đang được in (đầu tệp so với cuối tệp)?
wvducky

1
@wvducky Tôi không có số liệu cứng, nhưng một nhược điểm của lần đầu tiên sử dụng đuôi theo sau là "đầu -1" là bạn cần biết trước tổng chiều dài. Nếu bạn không biết điều đó, bạn sẽ phải tính nó trước, đây sẽ là một sự mất mát về hiệu suất. Một nhược điểm khác là nó ít trực quan để sử dụng. Chẳng hạn, nếu bạn có số 1 đến 10 và bạn muốn lấy dòng thứ 3, bạn sẽ phải sử dụng "tail -8 | head -1". Đó là lỗi dễ xảy ra hơn "đầu -3 | đuôi -1".
Philipp Claßen

xin lỗi, tôi nên có một ví dụ để rõ ràng head -5 | tail -1vs tail -n+5 | head -1. Trên thực tế, tôi tìm thấy một câu trả lời khác đã làm một phép so sánh thử nghiệm và thấy tail | headlà nhanh hơn. stackoverflow.com/a/48189289
wvducky

1
@wvducky Cảm ơn bạn đã đề cập đến nó! Tôi đã làm một số thử nghiệm và phải đồng ý rằng nó luôn nhanh hơn một chút, không phụ thuộc vào vị trí của dòng so với những gì tôi thấy. Cho rằng, tôi đã thay đổi câu trả lời của mình và cũng bao gồm điểm chuẩn trong trường hợp ai đó muốn sao chép nó.
Philipp Claßen

27

Wow, tất cả các khả năng!

Thử cái này:

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

hoặc một trong số này tùy thuộc vào phiên bản Awk của bạn:

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

( Bạn có thể phải thử nawkhoặc gawklệnh ).

Có một công cụ chỉ in dòng cụ thể đó không? Không phải là một trong những công cụ tiêu chuẩn. Tuy nhiên, sedcó lẽ là gần nhất và đơn giản nhất để sử dụng.



21

Câu hỏi này được gắn thẻ Bash, đây là cách làm của Bash (≥4): sử dụng mapfilevới tùy chọn -s(bỏ qua) và -n(đếm).

Nếu bạn cần lấy dòng thứ 42 của một tệp file:

mapfile -s 41 -n 1 ary < file

Tại thời điểm này, bạn sẽ có một mảng arycác trường chứa các dòng file(bao gồm cả dòng mới), nơi chúng tôi đã bỏ qua 41 dòng đầu tiên ( -s 41) và dừng lại sau khi đọc một dòng ( -n 1). Vì vậy, đó thực sự là dòng thứ 42. Để in nó ra:

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

Nếu bạn cần một loạt các dòng, hãy nói phạm vi 42 Mạnh666 (bao gồm) và nói rằng bạn không muốn tự mình làm toán và in chúng trên thiết bị xuất chuẩn:

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

Nếu bạn cũng cần xử lý các dòng này, việc lưu trữ dòng mới này không thực sự thuận tiện. Trong trường hợp này, sử dụng -ttùy chọn (trim):

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

Bạn có thể có một chức năng làm điều đó cho bạn:

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[@]}"
}

Không có lệnh bên ngoài, chỉ có nội dung Bash!


11

Bạn cũng có thể sử dụng sed print và thoát:

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

6
Các -ntùy chọn vô hiệu hóa các hành động mặc định để in mỗi dòng, như chắc chắn bạn sẽ phát hiện ra bằng cách xem lướt qua các trang người đàn ông.
tripleee

Trong GNU sed tất cả các sedcâu trả lời đều có cùng tốc độ. Do đó (đối với GNU sed ) đây là sedcâu trả lời tốt nhất , vì nó sẽ tiết kiệm thời gian cho các tệp lớn và giá trị dòng thứ n nhỏ .
agc

7

Bạn cũng có thể sử dụng Perl cho việc này:

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

6

Giải pháp nhanh nhất cho các tệp lớn luôn là đuôi | đầu, với điều kiện là hai khoảng cách:

  • từ đầu tập tin đến dòng bắt đầu. Hãy gọi nóS
  • khoảng cách từ dòng cuối cùng đến cuối tập tin. Là nóE

được biêt đên. Sau đó, chúng ta có thể sử dụng điều này:

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

howmany chỉ là số lượng dòng yêu cầu.

Một số chi tiết khác trong https://unix.stackexchange.com/a/216614/79743


1
Vui lòng làm rõ các đơn vị SE, (ví dụ byte, ký tự hoặc dòng).
agc

6

Tất cả các câu trả lời trên trực tiếp trả lời câu hỏi. Nhưng đây là một giải pháp ít trực tiếp hơn nhưng là một ý tưởng quan trọng hơn, để kích động tư tưởng.

Vì độ dài dòng là tùy ý, tất cả các byte của tệp trước dòng thứ n cần phải được đọc. Nếu bạn có một tệp khổng lồ hoặc cần lặp lại tác vụ này nhiều lần và quá trình này tốn thời gian, thì bạn nên nghiêm túc suy nghĩ về việc liệu bạn có nên lưu trữ dữ liệu của mình theo cách khác ở nơi đầu tiên không.

Giải pháp thực sự là có một chỉ mục, ví dụ như ở phần đầu của tệp, cho biết các vị trí bắt đầu các dòng. Bạn có thể sử dụng định dạng cơ sở dữ liệu hoặc chỉ cần thêm một bảng vào đầu tệp. Hoặc tạo một tệp chỉ mục riêng để đi kèm với tệp văn bản lớn của bạn.

ví dụ: bạn có thể tạo danh sách các vị trí ký tự cho dòng mới:

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

sau đó đọc với tail, mà thực sự seeks trực tiếp đến điểm thích hợp trong tập tin!

ví dụ: để có được dòng 1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • Điều này có thể không hoạt động với các ký tự 2 byte / multibyte, vì awk là "nhận biết ký tự" nhưng đuôi thì không.
  • Tôi đã không kiểm tra điều này đối với một tập tin lớn.
  • Cũng xem câu trả lời này .
  • Hoặc - chia tệp của bạn thành các tệp nhỏ hơn!

5

Là một câu trả lời cho câu trả lời điểm chuẩn rất hữu ích của CaffeineConnoisseur ... Tôi tò mò về việc phương pháp 'mapfile' được so sánh nhanh như thế nào (vì nó chưa được thử nghiệm), vì vậy tôi đã thử tự so sánh tốc độ nhanh và bẩn Tôi có bash 4 tiện dụng. Đã thử nghiệm phương pháp "đuôi | đầu" (chứ không phải đầu | đuôi) được đề cập trong một trong những bình luận về câu trả lời hàng đầu trong khi tôi đang ở đó, vì mọi người đang hát những lời ca ngợi. Tôi không có bất cứ thứ gì gần bằng kích thước của tệp thử nghiệm được sử dụng; thứ tốt nhất tôi có thể tìm thấy trong một thông báo ngắn là một tệp phả hệ 14M (các dòng dài được phân tách bằng khoảng trắng, chỉ dưới 12000 dòng).

Phiên bản ngắn: mapfile xuất hiện nhanh hơn phương thức cắt, nhưng chậm hơn mọi thứ khác, vì vậy tôi gọi nó là một người siêng năng. đuôi | đầu, OTOH, có vẻ như nó có thể là nhanh nhất, mặc dù với một tệp có kích thước này, sự khác biệt không đáng kể so với 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

Hi vọng điêu nay co ich!


4

Sử dụng những gì người khác đề cập, tôi muốn đây là một chức năng nhanh chóng và khéo léo trong vỏ bash của tôi.

Tạo một tệp: ~/.functions

Thêm vào đó nội dung:

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

Sau đó thêm nó vào ~/.bash_profile:

source ~/.functions

Bây giờ khi bạn mở một cửa sổ bash mới, bạn chỉ có thể gọi hàm như vậy:

getline 441 myfile.txt


3

Nếu bạn có nhiều dòng bằng cách phân cách bằng \ n (dòng thường mới). Bạn cũng có thể sử dụng 'cắt':

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

Bạn sẽ nhận được dòng thứ 2 từ tập tin. -f3mang đến cho bạn dòng thứ 3.


1
Cũng có thể được sử dụng để hiển thị nhiều dòng: cat FILE | cut -f2,5 -d$'\n'sẽ hiển thị dòng 2 và 5 của TẬP TIN. (Nhưng nó sẽ không giữ được trật tự.)
Andriy Makukha

2

Để in dòng thứ n bằng cách sử dụng sed với một biến như số dòng:

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

Ở đây, cờ '-e' là để thêm tập lệnh vào lệnh được thực thi.


2
Dấu hai chấm là một lỗi cú pháp và phải là dấu chấm phẩy.
tripleee 16/2/2016

2

Rất nhiều câu trả lời hay. Cá nhân tôi đi với awk. Để thuận tiện, nếu bạn sử dụng bash, chỉ cần thêm bên dưới vào của bạn ~/.bash_profile. Và, lần sau khi bạn đăng nhập (hoặc nếu bạn lấy nguồn .bash_profile sau bản cập nhật này), bạn sẽ có một chức năng "nth" tiện lợi mới có sẵn để dẫn các tệp của bạn đi qua.

Thực hiện điều này hoặc đặt nó vào ~ / .bash_profile (nếu sử dụng bash) và mở lại bash (hoặc thực thi source ~/.bach_profile)

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

Sau đó, để sử dụng nó, chỉ cần đường ống qua nó. Ví dụ,:

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


1

Sau khi tham gia một cái nhìn tại các câu trả lời trêncác chuẩn mực , tôi đã thực hiện một chức năng helper nhỏ:

function nth {
    if (( ${#} < 1 || ${#} > 2 )); then
        echo -e "usage: $0 \e[4mline\e[0m [\e[4mfile\e[0m]"
        return 1
    fi
    if (( ${#} > 1 )); then
        sed "$1q;d" $2
    else
        sed "$1q;d"
    fi
}

Về cơ bản, bạn có thể sử dụng nó trong hai thời trang:

nth 42 myfile.txt
do_stuff | nth 42

0

Tôi đã đặt một số câu trả lời ở trên vào một kịch bản bash ngắn mà bạn có thể đưa vào một tập tin gọi get.shvà liên kết đến /usr/local/bin/get(hoặc bất kỳ tên khác mà bạn thích).

#!/bin/bash
if [ "${1}" == "" ]; then
    echo "error: blank line number";
    exit 1
fi
re='^[0-9]+$'
if ! [[ $1 =~ $re ]] ; then
    echo "error: line number arg not a number";
    exit 1
fi
if [ "${2}" == "" ]; then
    echo "error: blank file name";
    exit 1
fi
sed "${1}q;d" $2;
exit 0

Đảm bảo đó là thực thi với

$ chmod +x get

Liên kết nó để làm cho nó có sẵn trên PATHvới

$ ln -s get.sh /usr/local/bin/get

Thưởng thức có trách nhiệm!

P

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.