Apakah ada cara "kanonik" untuk melakukan itu? Saya telah menggunakan head -n | tail -1
yang 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.
awk
dansed
dan saya yakin seseorang dapat datang dengan Perl one-liner atau lebih;)head | tail
solusi tersebut kurang optimal. Solusi lain yang hampir optimal telah disarankan.head | tail
solusi tidak bekerja, jika Anda query baris yang tidak ada di input: akan mencetak baris terakhir.Jawaban:
head
dan pipa dengantail
akan lambat untuk file besar. Saya akan menyarankansed
seperti ini:Di mana
NUM
nomor baris yang ingin Anda cetak; jadi, misalnya,sed '10q;d' file
untuk mencetak baris ke 10file
.Penjelasan:
NUMq
akan segera berhenti ketika nomor barisnyaNUM
.d
akan menghapus baris alih-alih mencetaknya; ini terhambat pada baris terakhir karenaq
menyebabkan sisa skrip dilewati ketika berhenti.Jika Anda memiliki
NUM
variabel, Anda ingin menggunakan tanda kutip ganda dan bukan tunggal:sumber
sed -n 'NUMp'
dansed 'NUM!d'
solusi yang diusulkan di bawah ini.tail -n+NUM file | head -n1
kemungkinan 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.cat
memang 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), sementaracat
kinerja tetap sama. Anehnya, pada OS X 10.9.3 semua ini tampaknya tidak membuat perbedaan:cat
/ tidakcat
, file di-cache atau tidak. @anubhava: kesenangan saya.sed 'NUMq
akan menampilkanNUM
file pertama dan;d
akan menghapus semua kecuali baris terakhir.akan mencetak baris ke-2
Baris 2011
baris 10 hingga baris 33
Baris 1 dan 3
dan seterusnya...
Untuk menambahkan garis dengan sed, Anda dapat memeriksa ini:
sed: masukkan garis pada posisi tertentu
sumber
<
dalam hal ini tidak perlu. Sederhananya, itu adalah preferensi saya menggunakan pengalihan, karena saya sering menggunakan pengalihan sepertised -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) :)head
/tail
tidak menyelesaikansed -n '1p;3p'
skenario - alias mencetak lebih banyak baris yang tidak berdekatan ...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:
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
time
built-in untuk benchmark setiap perintah.Baseline
Pertama mari kita lihat bagaimana
head
tail
solusinya: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:
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
exit
karena saya tidak akan menunggu file lengkap berjalan: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:
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:
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
tail
solusinya. Paling-palingsed
solusi ini memberikan peningkatan efisiensi ~ 3%.(persentase dihitung dengan rumus
% = (runtime/baseline - 1) * 100
)Baris 50,000,000
sed
perl
head|tail
awk
cut
Baris 500.000.000
sed
perl
head|tail
awk
cut
Baris 3.338.559.320
sed
perl
head|tail
awk
cut
sumber
Dengan
awk
cukup cepat:Bila ini benar, perilaku default
awk
yaitu dilakukan:{print $0}
.Versi alternatif
Jika file Anda berukuran besar, sebaiknya Anda
exit
membaca baris yang diperlukan. Dengan cara ini Anda menghemat waktu CPU Lihat perbandingan waktu di akhir jawaban .Jika Anda ingin memberikan nomor baris dari variabel bash, Anda dapat menggunakan:
Lihat berapa banyak waktu yang dihemat dengan menggunakan
exit
, khususnya jika garis kebetulan berada di bagian pertama file:Jadi perbedaannya adalah 0,198 vs 1,303, sekitar 6x lebih cepat.
sumber
awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3
. Dengan GNU awk ini dapat dipercepat menggunakanawk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3
.FS=RS
menghindari pemisahan bidang?FS=RS
tidak menghindari pemisahan bidang, tetapi hanya mem-parsing $ 0 dan hanya menetapkan satu bidang karena tidak adaRS
di$0
FS=RS
dan tidak melihat perbedaan pada timing. Bagaimana dengan saya mengajukan pertanyaan tentang hal itu sehingga Anda dapat memperluas? Terima kasih!Menurut tes saya, dalam hal kinerja dan keterbacaan rekomendasi saya adalah:
tail -n+N | head -1
N
adalah nomor baris yang Anda inginkan. Misalnya,tail -n+7 input.txt | head -1
akan mencetak baris ke 7 file tersebut.tail -n+N
akan mencetak semuanya mulai dari barisN
, danhead -1
akan membuatnya berhenti setelah satu baris.Alternatifnya
head -N | tail -1
mungkin 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 dtkhead -N | tail -1
: 4,6 dtksed Nq;d
: 18,8 dtkHasil mungkin berbeda, tetapi kinerja
head | tail
dantail | head
, secara umum, sebanding untuk input yang lebih kecil, dansed
selalu 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:
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:
sumber
head | tail
vstail | head
? Atau apakah itu tergantung pada baris mana yang sedang dicetak (awal file vs akhir file)?head -5 | tail -1
vstail -n+5 | head -1
. Sebenarnya, saya menemukan jawaban lain yang melakukan perbandingan tes dan ternyatatail | head
lebih cepat. stackoverflow.com/a/48189289Wow, semua kemungkinan!
Coba ini:
atau salah satunya tergantung pada versi Awk Anda:
( Anda mungkin harus mencoba
nawk
ataugawk
perintah ).Apakah ada alat yang hanya mencetak garis tertentu? Bukan salah satu alat standar. Namun,
sed
mungkin yang paling dekat dan paling sederhana untuk digunakan.sumber
Script satu baris yang berguna untuk sed
sumber
Pertanyaan ini ditandai dengan Bash, inilah cara melakukan Bash (≥4): gunakan
mapfile
dengan opsi-s
(lewati) dan-n
(hitung).Jika Anda perlu mendapatkan baris file ke-42
file
:Pada titik ini, Anda akan memiliki array
ary
yang bidangnya berisi barisfile
(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:Jika Anda membutuhkan rentang garis, ucapkan kisaran 42-666 (inklusif), dan katakan Anda tidak ingin menghitung sendiri, dan cetaklah di stdout:
Jika Anda perlu memproses garis-garis ini juga, itu tidak benar-benar nyaman untuk menyimpan baris tambahan. Dalam hal ini gunakan
-t
opsi (trim):Anda dapat memiliki fungsi untuk melakukannya:
Tidak ada perintah eksternal, hanya Bash bawaan!
sumber
Anda juga dapat menggunakan cetak sed dan keluar:
sumber
-n
pilihan menonaktifkan tindakan default untuk mencetak setiap baris, karena pasti Anda akan menemukan dengan sekilas di halaman manual.sed
semuased
jawabannya adalah tentang kecepatan yang sama. Oleh karena itu (untuk GNUsed
) ini adalahsed
jawaban terbaik , karena itu akan menghemat waktu untuk file besar dan nilai baris n kecil .Anda juga dapat menggunakan Perl untuk ini:
sumber
Solusi tercepat untuk file besar selalu tail | head, asalkan dua jarak:
S
E
dikenal. Lalu, kita bisa menggunakan ini:
berapa banyak hanya hitungan garis yang diperlukan.
Beberapa detail lainnya di https://unix.stackexchange.com/a/216614/79743
sumber
S
danE
, (yaitu byte, karakter, atau baris).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:
kemudian baca dengan
tail
, yang sebenarnyaseek
s langsung ke titik yang sesuai dalam file!mis. untuk mendapatkan baris 1000:
sumber
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.
Semoga ini membantu!
sumber
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
sumber
Jika Anda mendapatkan beberapa baris dengan dibatasi oleh \ n (baris biasanya baru). Anda dapat menggunakan 'cut' juga:
Anda akan mendapatkan baris ke-2 dari file.
-f3
memberi Anda garis ke-3.sumber
cat FILE | cut -f2,5 -d$'\n'
akan menampilkan baris 2 dan 5 FILE. (Tapi itu tidak akan menjaga ketertiban.)Untuk mencetak baris ke-n menggunakan sed dengan variabel sebagai nomor baris:
Di sini bendera '-e' adalah untuk menambahkan skrip ke perintah yang akan dieksekusi.
sumber
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
sumber
Setelah mengambil melihat jawaban atas dan yang patokan , saya telah menerapkan fungsi pembantu kecil:
Pada dasarnya Anda dapat menggunakannya dalam dua mode:
sumber
Saya telah memasukkan beberapa jawaban di atas ke dalam skrip bash pendek yang dapat Anda masukkan ke file yang dipanggil
get.sh
dan ditautkan/usr/local/bin/get
(atau nama lain apa pun yang Anda inginkan).Pastikan itu dapat dieksekusi dengan
Tautkan untuk membuatnya tersedia saat
PATH
bersamaNikmati secara bertanggung jawab!
P
sumber