Alat Bash untuk mendapatkan baris ke-n dari file

606

Apakah ada cara "kanonik" untuk melakukan itu? Saya telah menggunakan head -n | tail -1yang melakukan trik, tetapi saya bertanya-tanya apakah ada alat Bash yang secara khusus mengekstrak baris (atau berbagai baris) dari file.

Yang dimaksud dengan "kanonik" adalah program yang fungsi utamanya adalah melakukan itu.

Vlad Vivdovitch
sumber
10
"Cara Unix" adalah untuk menghubungkan alat yang melakukan pekerjaannya dengan baik. Jadi saya pikir Anda sudah menemukan metode yang sangat cocok. Metode lain termasuk awkdan seddan saya yakin seseorang dapat datang dengan Perl one-liner atau lebih;)
0xC0000022L
3
Perintah ganda menunjukkan bahwa head | tailsolusi tersebut kurang optimal. Solusi lain yang hampir optimal telah disarankan.
Jonathan Leffler
Sudahkah Anda menjalankan tolok ukur pada solusi mana yang tercepat untuk kasus rata-rata?
Marcin
5
Benchmark (untuk rentang) di cat line X ke line Y pada file besar di Unix & Linux . (cc @Marcin, kalau-kalau Anda masih bertanya-tanya setelah dua + tahun)
Kevin
6
The head | tailsolusi tidak bekerja, jika Anda query baris yang tidak ada di input: akan mencetak baris terakhir.
jarno

Jawaban:

803

headdan pipa dengan tailakan lambat untuk file besar. Saya akan menyarankan sedseperti ini:

sed 'NUMq;d' file

Di mana NUMnomor baris yang ingin Anda cetak; jadi, misalnya, sed '10q;d' fileuntuk mencetak baris ke 10 file.

Penjelasan:

NUMqakan segera berhenti ketika nomor barisnya NUM.

dakan menghapus baris alih-alih mencetaknya; ini terhambat pada baris terakhir karena qmenyebabkan sisa skrip dilewati ketika berhenti.

Jika Anda memiliki NUMvariabel, Anda ingin menggunakan tanda kutip ganda dan bukan tunggal:

sed "${NUM}q;d" file
anubhava
sumber
44
Bagi mereka bertanya-tanya, solusi ini tampaknya sekitar 6 sampai 9 kali lebih cepat dari sed -n 'NUMp'dan sed 'NUM!d'solusi yang diusulkan di bawah ini.
Skippy le Grand Gourou
75
Saya pikir tail -n+NUM file | head -n1kemungkinan akan sama cepat atau lebih cepat. Setidaknya, itu (secara signifikan) lebih cepat pada sistem saya ketika saya mencobanya dengan NUM menjadi 2.500 pada file dengan setengah juta baris. YMMV, tapi saya tidak benar-benar mengerti mengapa itu terjadi.
rici
2
@rici (revisi komentar sebelumnya) Di Linux (Ubuntu 12.04, Fedora 20), menggunakan catmemang lebih cepat (hampir dua kali lebih cepat), tetapi hanya jika file belum di-cache . Setelah file di-cache , penggunaan langsung argumen nama file lebih cepat (sekitar 1/3 lebih cepat), sementara catkinerja tetap sama. Anehnya, pada OS X 10.9.3 semua ini tampaknya tidak membuat perbedaan: cat/ tidak cat, file di-cache atau tidak. @anubhava: kesenangan saya.
mklement0
2
@ SkippyleGrandGourou: Mengingat sifat spesifik dari optimasi ini , bahkan rentang angka Anda tidak ada gunanya sebagai pernyataan umum . Satu-satunya kesimpulan umum adalah ini: (a) optimisasi ini dapat diterapkan dengan aman ke semua input, (b) efeknya akan berkisar dari tidak ada hingga dramatis , tergantung pada indeks garis yang dicari relatif terhadap jumlah garis keseluruhan.
mklement0
17
sed 'NUMqakan menampilkan NUMfile pertama dan ;dakan menghapus semua kecuali baris terakhir.
anubhava
304
sed -n '2p' < file.txt

akan mencetak baris ke-2

sed -n '2011p' < file.txt

Baris 2011

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

baris 10 hingga baris 33

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

Baris 1 dan 3

dan seterusnya...

Untuk menambahkan garis dengan sed, Anda dapat memeriksa ini:

sed: masukkan garis pada posisi tertentu

jm666
sumber
6
@RafaelBarbosa <dalam hal ini tidak perlu. Sederhananya, itu adalah preferensi saya menggunakan pengalihan, karena saya sering menggunakan pengalihan seperti sed -n '100p' < <(some_command)- jadi, sintaks universal :). Ini TIDAK kurang efektif, karena pengalihan dilakukan dengan shell ketika bercabang sendiri, jadi ... itu hanya preferensi ... (dan ya, itu adalah satu karakter lagi) :)
jm666
1
@ jm666 Sebenarnya ini 2 karakter lebih lama karena Anda biasanya akan meletakkan '<' dan juga ruang tambahan '' setelah <sebagai lawan hanya satu ruang jika Anda tidak menggunakan <:)
rasen58
2
@ rasen58 ruang adalah karakter juga? :) / oke, hanya bercanda - kamu benar / :)
jm666
1
@ Wahaime tentu saja, jika seseorang perlu melakukan optimasi. Tapi IMHO untuk masalah "umum" itu ok dan perbedaannya tidak terlalu mencolok. Juga, head/ tailtidak menyelesaikan sed -n '1p;3p'skenario - alias mencetak lebih banyak baris yang tidak berdekatan ...
jm666
1
@ Wahaime tentu saja - catatan itu benar dan dibutuhkan. :)
jm666
93

Saya memiliki situasi unik di mana saya dapat membandingkan solusi yang diusulkan pada halaman ini, jadi saya menulis jawaban ini sebagai konsolidasi dari solusi yang diusulkan dengan menyertakan waktu berjalan untuk masing-masing.

Mendirikan

Saya memiliki file data teks ASCII 3,261 gigabyte dengan satu pasangan nilai kunci per baris. File ini berisi total 3.339.550.320 baris dan menentang pembukaan di editor apa pun yang saya coba, termasuk masuk ke Vim saya. Saya perlu mengatur ulang file ini untuk menyelidiki beberapa nilai yang saya temukan hanya sekitar baris ~ 500.000.000.

Karena file memiliki begitu banyak baris:

  • Saya perlu mengekstrak hanya sebagian dari baris untuk melakukan sesuatu yang berguna dengan data.
  • Membaca setiap baris yang mengarah ke nilai-nilai yang saya pedulikan akan memakan waktu lama.
  • Jika solusinya membaca melewati baris yang saya pedulikan dan terus membaca sisa file itu akan membuang waktu membaca hampir 3 miliar baris yang tidak relevan dan memakan waktu 6x lebih lama dari yang diperlukan.

Skenario terbaik saya adalah solusi yang mengekstrak hanya satu baris dari file tanpa membaca baris lain dalam file, tetapi saya tidak bisa memikirkan bagaimana saya akan mencapai ini di Bash.

Untuk keperluan kewarasan saya, saya tidak akan mencoba membaca 500.000.000 baris penuh yang saya butuhkan untuk masalah saya sendiri. Sebaliknya saya akan mencoba untuk mengekstrak baris 50.000.000 dari 3.339.550.320 (yang berarti membaca file lengkap akan memakan waktu 60x lebih lama dari yang diperlukan).

Saya akan menggunakan timebuilt-in untuk benchmark setiap perintah.

Baseline

Pertama mari kita lihat bagaimana head tailsolusinya:

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

real    1m15.321s

Garis dasar untuk baris 50 juta adalah 00: 01: 15.321, jika saya langsung ke baris 500 juta mungkin akan ~ 12,5 menit.

memotong

Saya ragu dengan yang ini, tapi patut dicoba:

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

real    5m12.156s

Yang ini membutuhkan waktu 00: 05: 12.156 untuk berjalan, yang jauh lebih lambat dari baseline! Saya tidak yakin apakah itu membaca seluruh file atau hanya hingga 50 juta baris sebelum berhenti, tetapi terlepas dari ini sepertinya bukan solusi yang layak untuk masalah ini.

AWK

Saya hanya menjalankan solusi dengan exitkarena saya tidak akan menunggu file lengkap berjalan:

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

real    1m16.583s

Kode ini berjalan pada 00: 01: 16.583, yang hanya lebih lambat ~ 1 detik, tetapi masih belum ada perbaikan pada baseline. Pada tingkat ini jika perintah keluar telah dikecualikan mungkin akan memakan waktu sekitar ~ 76 menit untuk membaca seluruh file!

Perl

Saya menjalankan solusi Perl yang ada juga:

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

real    1m13.146s

Kode ini berjalan di 00: 01: 13.146, yang ~ 2 detik lebih cepat dari baseline. Jika saya menjalankannya pada 500.000.000 penuh mungkin akan memakan waktu ~ 12 menit.

sed

Jawaban teratas di papan tulis, inilah hasil saya:

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

real    1m12.705s

Kode ini berjalan di 00: 01: 12.705, yang 3 detik lebih cepat dari garis dasar, dan ~ 0,4 detik lebih cepat dari Perl. Jika saya menjalankannya pada baris 500.000.000 penuh mungkin akan memakan waktu ~ 12 menit.

mapfile

Saya memiliki bash 3.1 dan karenanya tidak dapat menguji solusi mapfile.

Kesimpulan

Sepertinya, sebagian besar, sulit untuk memperbaiki head tailsolusinya. Paling-paling sedsolusi ini memberikan peningkatan efisiensi ~ 3%.

(persentase dihitung dengan rumus % = (runtime/baseline - 1) * 100)

Baris 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

Baris 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

Baris 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
CaffeineConnoisseur
sumber
4
Saya bertanya-tanya berapa lama hanya mengambil seluruh file ke / dev / null akan memakan waktu. (Bagaimana jika ini hanya patokan hard disk?)
sanmai
Saya merasakan dorongan jahat untuk tunduk pada kepemilikan kamus file teks 3+ pertunjukan Anda. Apa pun alasannya, ini sangat mencakup
tekstur
51

Dengan awkcukup cepat:

awk 'NR == num_line' file

Bila ini benar, perilaku default awkyaitu dilakukan: {print $0}.


Versi alternatif

Jika file Anda berukuran besar, sebaiknya Anda exitmembaca baris yang diperlukan. Dengan cara ini Anda menghemat waktu CPU Lihat perbandingan waktu di akhir jawaban .

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

Jika Anda ingin memberikan nomor baris dari variabel bash, Anda dapat menggunakan:

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

Lihat berapa banyak waktu yang dihemat dengan menggunakan exit, khususnya jika garis kebetulan berada di bagian pertama file:

# 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

Jadi perbedaannya adalah 0,198 vs 1,303, sekitar 6x lebih cepat.

fedorqui 'SO berhenti merugikan'
sumber
Metode ini akan selalu lebih lambat karena upaya awk untuk melakukan pemisahan bidang. Overhead pemisahan lapangan dapat dikurangi denganawk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
kvantour
Kekuatan sebenarnya dari awk dalam metode ini datang balik ketika Anda ingin baris n1 concatenate dari file1, n2 dari file2, n3 atau file3 ... awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3. Dengan GNU awk ini dapat dipercepat menggunakan awk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3.
kvantour
@ ramah memang, nextfile GNU awk sangat bagus untuk hal-hal seperti itu. Kenapa FS=RSmenghindari pemisahan bidang?
fedorqui 'SO stop harming'
1
FS=RStidak menghindari pemisahan bidang, tetapi hanya mem-parsing $ 0 dan hanya menetapkan satu bidang karena tidak ada RSdi$0
kvantour
@vantour Saya sudah melakukan beberapa tes dengan FS=RSdan tidak melihat perbedaan pada timing. Bagaimana dengan saya mengajukan pertanyaan tentang hal itu sehingga Anda dapat memperluas? Terima kasih!
fedorqui 'SO stop harming'
29

Menurut tes saya, dalam hal kinerja dan keterbacaan rekomendasi saya adalah:

tail -n+N | head -1

Nadalah nomor baris yang Anda inginkan. Misalnya, tail -n+7 input.txt | head -1akan mencetak baris ke 7 file tersebut.

tail -n+Nakan mencetak semuanya mulai dari baris N, dan head -1akan membuatnya berhenti setelah satu baris.


Alternatifnya head -N | tail -1mungkin sedikit lebih mudah dibaca. Misalnya, ini akan mencetak baris ke-7:

head -7 input.txt | tail -1

Ketika datang ke kinerja, tidak ada banyak perbedaan untuk ukuran yang lebih kecil, tetapi akan dikalahkan oleh tail | head(dari atas) ketika file menjadi besar.

Pilihan teratas sed 'NUMq;d'menarik untuk diketahui, tetapi saya berpendapat bahwa itu akan dipahami oleh lebih sedikit orang di luar kotak daripada solusi kepala / ekor dan juga lebih lambat dari pada ekor / kepala.

Dalam pengujian saya, kedua versi ekor / kepala mengungguli sed 'NUMq;d'secara konsisten. Itu sejalan dengan tolok ukur lain yang diposting. Sulit untuk menemukan kasus di mana ekor / kepala benar-benar buruk. Ini juga tidak mengejutkan, karena ini adalah operasi yang Anda harapkan akan sangat dioptimalkan dalam sistem Unix modern.

Untuk mendapatkan gambaran tentang perbedaan kinerja, ini adalah angka yang saya dapatkan untuk file besar (9.3G):

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

Hasil mungkin berbeda, tetapi kinerja head | taildan tail | head, secara umum, sebanding untuk input yang lebih kecil, dan sedselalu lebih lambat oleh faktor yang signifikan (sekitar 5x atau lebih).

Untuk mereproduksi tolok ukur saya, Anda dapat mencoba yang berikut, tetapi berhati-hatilah bahwa itu akan membuat file 9.3G di direktori kerja saat ini:

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

Ini adalah output dari pelarian pada mesin saya (ThinkPad X1 Carbon dengan SSD dan memori 16G). Saya berasumsi pada akhirnya menjalankan semuanya akan datang dari cache, bukan dari disk:

*** 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
Philipp Claßen
sumber
1
Apakah kinerja berbeda antara head | tailvs tail | head? Atau apakah itu tergantung pada baris mana yang sedang dicetak (awal file vs akhir file)?
wisbucky
1
@wisbucky Saya tidak memiliki angka yang sulit, tetapi satu kelemahan dari menggunakan ekor pertama diikuti oleh "kepala -1" adalah bahwa Anda perlu mengetahui panjang total di muka. Jika Anda tidak mengetahuinya, Anda harus menghitungnya terlebih dahulu, yang akan menjadi kehilangan performa. Kerugian lain adalah kurang intuitif untuk digunakan. Misalnya, jika Anda memiliki angka 1 hingga 10 dan Anda ingin mendapatkan baris ke-3, Anda harus menggunakan "tail -8 | head -1". Itu lebih rentan kesalahan daripada "head -3 | tail -1".
Philipp Claßen
maaf, saya harus memasukkan contoh yang jelas. head -5 | tail -1vs tail -n+5 | head -1. Sebenarnya, saya menemukan jawaban lain yang melakukan perbandingan tes dan ternyata tail | headlebih cepat. stackoverflow.com/a/48189289
wisbucky
1
@wisbucky Terima kasih telah menyebutkannya! Saya melakukan beberapa tes dan harus setuju bahwa itu selalu sedikit lebih cepat, terlepas dari posisi garis dari apa yang saya lihat. Mengingat itu, saya mengubah jawaban saya dan juga memasukkan tolok ukur jika seseorang ingin mereproduksinya.
Philipp Claßen
27

Wow, semua kemungkinan!

Coba ini:

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

atau salah satunya tergantung pada versi Awk Anda:

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

( Anda mungkin harus mencoba nawkatau gawkperintah ).

Apakah ada alat yang hanya mencetak garis tertentu? Bukan salah satu alat standar. Namun, sedmungkin yang paling dekat dan paling sederhana untuk digunakan.

David W.
sumber
21

Pertanyaan ini ditandai dengan Bash, inilah cara melakukan Bash (≥4): gunakan mapfiledengan opsi -s(lewati) dan -n(hitung).

Jika Anda perlu mendapatkan baris file ke-42 file:

mapfile -s 41 -n 1 ary < file

Pada titik ini, Anda akan memiliki array aryyang bidangnya berisi baris file(termasuk baris baru), tempat kami melewatkan 41 baris pertama ( -s 41), dan berhenti setelah membaca satu baris ( -n 1). Jadi itu benar-benar garis ke-42. Untuk mencetaknya:

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

Jika Anda membutuhkan rentang garis, ucapkan kisaran 42-666 (inklusif), dan katakan Anda tidak ingin menghitung sendiri, dan cetaklah di stdout:

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

Jika Anda perlu memproses garis-garis ini juga, itu tidak benar-benar nyaman untuk menyimpan baris tambahan. Dalam hal ini gunakan -topsi (trim):

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

Anda dapat memiliki fungsi untuk melakukannya:

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

Tidak ada perintah eksternal, hanya Bash bawaan!

gniourf_gniourf
sumber
11

Anda juga dapat menggunakan cetak sed dan keluar:

sed -n '10{p;q;}' file   # print line 10
bernd
sumber
6
The -npilihan menonaktifkan tindakan default untuk mencetak setiap baris, karena pasti Anda akan menemukan dengan sekilas di halaman manual.
tripleee
Dalam GNU sed semua sedjawabannya adalah tentang kecepatan yang sama. Oleh karena itu (untuk GNU sed ) ini adalah sedjawaban terbaik , karena itu akan menghemat waktu untuk file besar dan nilai baris n kecil .
agc
7

Anda juga dapat menggunakan Perl untuk ini:

perl -wnl -e '$.== NUM && print && exit;' some.file
Timofey Stolbov
sumber
6

Solusi tercepat untuk file besar selalu tail | head, asalkan dua jarak:

  • dari awal file ke baris awal. Mari kita sebut sajaS
  • jarak dari baris terakhir ke akhir file. Jadilah ituE

dikenal. Lalu, kita bisa menggunakan ini:

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

berapa banyak hanya hitungan garis yang diperlukan.

Beberapa detail lainnya di https://unix.stackexchange.com/a/216614/79743

Komunitas
sumber
1
Tolong jelaskan unit Sdan E, (yaitu byte, karakter, atau baris).
agc
6

Semua jawaban di atas langsung menjawab pertanyaan. Tapi ini solusi yang kurang langsung tetapi ide yang berpotensi lebih penting, untuk memancing pemikiran.

Karena panjang garis arbitrer, semua byte file sebelum baris ke-n perlu dibaca. Jika Anda memiliki file besar atau perlu mengulangi tugas ini berkali-kali, dan proses ini memakan waktu, maka Anda harus serius memikirkan apakah Anda harus menyimpan data Anda dengan cara yang berbeda di tempat pertama.

Solusi sebenarnya adalah memiliki indeks, misalnya di awal file, yang menunjukkan posisi di mana garis dimulai. Anda bisa menggunakan format database, atau cukup tambahkan tabel di awal file. Atau buat file indeks terpisah untuk menemani file teks besar Anda.

mis. Anda dapat membuat daftar posisi karakter untuk baris baru:

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

kemudian baca dengan tail, yang sebenarnya seeks langsung ke titik yang sesuai dalam file!

mis. untuk mendapatkan baris 1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • Ini mungkin tidak bekerja dengan karakter 2-byte / multibyte, karena awk adalah "karakter-aware" tetapi tail tidak.
  • Saya belum menguji ini terhadap file besar.
  • Lihat juga jawaban ini .
  • Atau - bagi file Anda menjadi file yang lebih kecil!
Sanjay Manohar
sumber
5

Sebagai tindak lanjut dari jawaban benchmark benchmark CaffeineConnoisseur yang sangat membantu ... Saya ingin tahu seberapa cepat metode 'mapfile' dibandingkan dengan yang lain (karena itu tidak diuji), jadi saya mencoba perbandingan kecepatan cepat dan kotor sendiri sebagai Saya punya bash 4 berguna. Melemparkan tes metode "ekor | kepala" (bukan kepala | ekor) yang disebutkan dalam salah satu komentar pada jawaban teratas ketika saya berada di sana, ketika orang-orang menyanyikan pujiannya. Saya tidak punya apa-apa hampir ukuran testfile yang digunakan; yang terbaik yang dapat saya temukan dalam waktu singkat adalah file 14M silsilah (garis panjang yang dipisahkan spasi, hanya di bawah 12.000 baris).

Versi pendek: mapfile muncul lebih cepat daripada metode cut, tetapi lebih lambat dari yang lainnya, jadi saya akan menyebutnya tak berguna. ekor | head, OTOH, sepertinya itu bisa menjadi yang tercepat, meskipun dengan ukuran file ini perbedaannya tidak terlalu besar dibandingkan dengan 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

Semoga ini membantu!

Jo Valentine-Cooper
sumber
4

Menggunakan apa yang disebutkan orang lain, saya ingin ini menjadi fungsi cepat & keren di shell bash saya.

Buat file: ~/.functions

Tambahkan ke dalamnya isinya:

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

Kemudian tambahkan ini ke ~/.bash_profile:

source ~/.functions

Sekarang ketika Anda membuka jendela bash baru, Anda bisa memanggil fungsi seperti ini:

getline 441 myfile.txt

Mark Shust di M.academy
sumber
3

Jika Anda mendapatkan beberapa baris dengan dibatasi oleh \ n (baris biasanya baru). Anda dapat menggunakan 'cut' juga:

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

Anda akan mendapatkan baris ke-2 dari file. -f3memberi Anda garis ke-3.

bahaya89
sumber
1
Dapat juga digunakan untuk menampilkan beberapa baris: cat FILE | cut -f2,5 -d$'\n'akan menampilkan baris 2 dan 5 FILE. (Tapi itu tidak akan menjaga ketertiban.)
Andriy Makukha
2

Untuk mencetak baris ke-n menggunakan sed dengan variabel sebagai nomor baris:

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

Di sini bendera '-e' adalah untuk menambahkan skrip ke perintah yang akan dieksekusi.

aliasav
sumber
2
Usus besar adalah kesalahan sintaksis, dan harus berupa titik koma.
tripleee
2

Sudah banyak jawaban bagus. Saya pribadi pergi dengan awk. Untuk kenyamanan, jika Anda menggunakan bash, cukup tambahkan di bawah ini untuk ~/.bash_profile. Dan, saat berikutnya Anda masuk (atau jika Anda mendapatkan .bash_profile setelah pembaruan ini), Anda akan memiliki fungsi "n" yang bagus dan baru yang tersedia untuk menyalurkan file Anda.

Jalankan ini atau letakkan di ~ / .bash_profile Anda (jika menggunakan bash) dan buka kembali bash (atau jalankan source ~/.bach_profile)

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

Kemudian, untuk menggunakannya, cukup pipa melalui itu. Misalnya,:

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

JJC
sumber
1

Setelah mengambil melihat jawaban atas dan yang patokan , saya telah menerapkan fungsi pembantu kecil:

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
}

Pada dasarnya Anda dapat menggunakannya dalam dua mode:

nth 42 myfile.txt
do_stuff | nth 42
Ulysse BN
sumber
0

Saya telah memasukkan beberapa jawaban di atas ke dalam skrip bash pendek yang dapat Anda masukkan ke file yang dipanggil get.shdan ditautkan /usr/local/bin/get(atau nama lain apa pun yang Anda inginkan).

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

Pastikan itu dapat dieksekusi dengan

$ chmod +x get

Tautkan untuk membuatnya tersedia saat PATHbersama

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

Nikmati secara bertanggung jawab!

P

mempolarisasikan
sumber