Ada "kanonik" cara untuk melakukan itu? I've telah menggunakan kepala -n | tail -1
yang melakukan trik, tapi aku've pernah bertanya-tanya jika ada's Bash alat yang secara khusus ekstrak garis (atau serangkaian garis) dari sebuah file.
Dengan "kanonik" maksudku program yang fungsi utamanya adalah untuk melakukan itu.
kepala
dan pipa dengan ekor
akan menjadi lambat untuk file besar. Saya akan menyarankan sed
seperti ini:
sed 'NUMq;d' file
Di mana NUM
adalah nomor baris yang ingin anda cetak; jadi, misalnya, sed '10q;d' file
untuk mencetak 10 baris dari file
.
Penjelasan:
NUMq
akan segera berhenti ketika nomor baris NUM
.
d
akan menghapus baris alih-alih mencetak; ini dihambat pada baris terakhir karena q
menyebabkan sisa naskah yang akan dilewati saat berhenti.
Jika anda memiliki NUM
dalam sebuah variabel, anda akan ingin untuk menggunakan tanda kutip ganda, bukan tunggal:
sed "${NUM}q;d" file
sed -n '2p' < file.txt
akan cetak 2 baris
sed -n '2011p' < file.txt
2011th baris
sed -n '10,33p' < file.txt
baris 10 sampai dengan line 33
sed -n '1p;3p' < file.txt
1 dan 3 baris
dan sebagainya...
Untuk menambahkan garis dengan sed, anda dapat memeriksa ini:
Saya memiliki situasi yang unik di mana saya bisa patokan solusi yang diusulkan pada halaman ini, dan jadi saya'm menulis jawaban ini sebagai konsolidasi dari solusi yang diusulkan dengan termasuk jangka waktu untuk masing-masing.
Mengatur
Saya memiliki 3.261 gigabyte ASCII text file data dengan satu pasangan key-value per baris. File ini berisi 3,339,550,320 baris total dan menentang pembukaan di setiap editor saya telah mencoba, termasuk saya pergi ke Vim. Saya perlu untuk subset file ini dalam rangka untuk menyelidiki beberapa nilai-nilai yang saya've ditemukan hanya mulai sekitar turut ~500,000,000.
Karena file telah begitu banyak baris:
Terbaik-kasus-skenario adalah solusi bahwa ekstrak hanya satu baris dari file tanpa membaca yang lain baris dalam file, tapi aku bisa't memikirkan bagaimana aku akan menyelesaikan ini di Bash.
Untuk keperluan kewarasan saya'm tidak akan mencoba untuk membaca penuh 500,000,000 baris I'd butuhkan untuk masalah saya sendiri. Bukannya aku'akan mencoba untuk mengekstrak baris 50,000,000 keluar dari 3,339,550,320 (yang berarti membaca berkas lengkap akan mengambil 60x lebih lama dari yang diperlukan).
Saya akan menggunakan waktu
built-in untuk acuan masing-masing perintah.
Dasar
Pertama let's melihat bagaimana kepala
ekor
solusi:
$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0
real 1m15.321s
Baseline untuk turut 50 juta 00:01:15.321, jika saya'd pergi lurus untuk turut 500 juta itu'd mungkin ~12.5 menit.
cut
I'm meragukan yang satu ini, tetapi itu's layak dicoba:
$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0
real 5m12.156s
Yang satu ini mengambil 00:05:12.156 untuk menjalankan, yang jauh lebih lambat dibandingkan baseline! I'm tidak yakin apakah itu membaca seluruh file atau hanya sampai ke garis 50 juta sebelum berhenti, tapi terlepas ini doesn't tampak seperti solusi yang layak untuk masalah ini.
AWK
Aku hanya berlari solusi dengan keluar
karena aku tidak't akan menunggu untuk file untuk menjalankan:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
Kode ini berlari di 00:01:16.583, yang hanya ~1 detik lebih lambat, tapi masih tidak perbaikan pada baseline. Pada tingkat ini jika keluar perintah telah dikeluarkan itu mungkin akan diambil sekitar ~76 menit untuk membaca seluruh file!
Perl
Aku berlari ada Perl solusi:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
Kode ini berlari di 00:01:13.146, yang adalah ~2 detik lebih cepat dari baseline. Jika saya'd jalankan di full 500,000,000 mungkin akan mengambil ~12 menit.
sed
Atas jawaban di papan tulis, di sini's my hasil:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
Kode ini berlari di 00:01:12.705, yaitu 3 detik lebih cepat dari baseline, dan ~0,4 detik lebih cepat dari Perl. Jika saya'd jalankan di full 500,000,000 baris itu mungkin akan diambil ~12 menit.
mapfile
Saya telah bash 3.1 dan oleh karena itu tidak dapat menguji mapfile solusi.
Kesimpulan
Sepertinya, untuk sebagian besar, itu's sulit untuk memperbaiki di atas kepala
ekor
solusi. Terbaik sed
solusi menyediakan ~3% peningkatan efisiensi.
(persentase dihitung dengan rumus % = (runtime/baseline - 1) * 100
)
Baris 50,000,000
sed
perl
kepala|ekor
awk
cut
Baris 500,000,000
sed
perl
kepala|ekor
awk
cut
Baris 3,338,559,320
sed
perl
kepala|ekor
awk
cut
Dengan awk
itu cukup cepat:
awk 'NR == num_line' file
Bila ini benar, perilaku default awk
dilakukan: {print $0}
.
Jika file anda terjadi untuk menjadi besar, anda'a baik keluar
setelah membaca baris yang diperlukan. Dengan cara ini anda menghemat waktu CPU.
awk 'NR == num_line {print; exit}' file
Jika anda ingin memberi nomor baris dari bash variabel yang dapat anda gunakan:
awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file # equivalent
Wow, semua kemungkinan!
Coba ini:
sed -n "${lineNum}p" $file
atau salah satu dari ini tergantung pada versi 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
(You mungkin harus mencoba nawk
atau melongo
command).
Apakah ada alat yang hanya mencetak baris tertentu? Tidak salah satu dari alat-alat standar. Namun, sed
mungkin adalah yang paling dekat dan paling sederhana untuk digunakan.
Menurut saya tes, dalam hal kinerja dan pembacaan 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.
tail -n+N
akan mencetak segala sesuatu mulai dari baris N
, dan kepala -1
akan membuatnya berhenti setelah satu baris.
Alternatif head -N | tail -1
mungkin sedikit lebih mudah dibaca. Misalnya, ini akan mencetak baris ke-7:
kepala -7 input.txt | tail -1
Ketika datang ke kinerja, tidak ada banyak perbedaan untuk ukuran yang lebih kecil, tapi itu akan mengungguli oleh ekor | head
(dari atas) ketika file menjadi besar.
Atas-sebagai sed 'NUMq;d'
adalah menarik untuk mengetahui, tapi saya berpendapat bahwa hal itu akan dimengerti oleh sedikit orang di luar kotak dari kepala/ekor solusi dan juga lebih lambat dari ekor/kepala.
Dalam tes saya, kedua ekor/heads versi mengungguli sed 'NUMq;d'
secara konsisten. Bahwa sejalan dengan tolok ukur lain yang telah diposting. Sulit untuk menemukan kasus di mana ekor/kepala itu benar-benar buruk. Hal ini juga tidak mengherankan, karena ini adalah operasi yang anda harapkan untuk menjadi sangat dioptimalkan dalam sistem Unix modern.
Untuk mendapatkan ide tentang perbedaan kinerja, ini adalah nomor yang saya dapatkan untuk file besar (9.3 G):
tail -n+N | head -1
: 3.7 detikhead -N | tail -1
: 4,6 detiksed Nq;d
: 18.8 secHasil mungkin berbeda, tetapi kinerja kepala | ekordan
ekor | head, secara umum, sebanding untuk masukan yang lebih kecil, dan
sed` selalu lebih lambat oleh faktor yang signifikan (sekitar 5x atau lebih).
Untuk mereproduksi patokan saya, anda dapat mencoba hal berikut, tetapi memperingatkan bahwa itu akan membuat 9.3 G file dalam 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
Berikut ini adalah output dari menjalankan di komputer saya (ThinkPad X1 Carbon dengan SSD dan memori 16G). Saya asumsikan di final menjalankan segala sesuatu 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
# print line number 52
sed '52!d' file
Pertanyaan ini yang ditandai Bash, di sini's Bash (≥4) cara melakukan: menggunakan mapfile
dengan s
(skip) dan -n
(menghitung) pilihan.
Jika anda perlu untuk mendapatkan ke-42 baris dari sebuah file file
:
mapfile -s 41 -n 1 ary < file
Pada titik ini, anda'll memiliki sebuah array ary
bidang yang berisi baris file
(termasuk tertinggal newline), di mana kita telah melewatkan pertama 41 garis (-s 41
), dan berhenti setelah membaca satu baris (-n 1
). Sehingga's benar-benar ke-42 baris. Untuk mencetaknya:
printf '%s' "${ary[0]}"
Jika anda membutuhkan berbagai garis, mengatakan berbagai 42-666 (inklusif), dan mengatakan anda don't ingin melakukan matematika sendiri, dan mencetak mereka pada stdout:
mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"
Jika anda perlu untuk proses garis-garis ini juga,'s tidak benar-benar nyaman untuk menyimpan baris baru mengikuti. Dalam hal ini gunakan -t
pilihan (trim):
mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"
Anda dapat memiliki fungsi melakukannya untuk anda:
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 builtins!
Solusi tercepat untuk file-file besar selalu ekor|kepala, asalkan dua jarak:
S
E
dikenal. Kemudian, kita bisa gunakan ini:
mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"
berapa banyak adalah jumlah baris yang diperlukan.
Beberapa lebih detail di https://unix.stackexchange.com/a/216614/79743
Semua jawaban diatas langsung menjawab pertanyaan. Tapi di sini's yang kurang langsung solusi tapi mungkin lebih penting ide, untuk memprovokasi pemikiran.
Karena panjang garis sewenang-wenang, semua byte dari file sebelum nth line perlu untuk dibaca. Jika anda memiliki sebuah file yang besar atau harus mengulangi tugas ini berkali-kali, dan proses ini memakan waktu, maka anda harus serius berpikir tentang apakah anda harus menyimpan data anda dalam cara yang berbeda di tempat pertama.
Solusi nyata adalah untuk memiliki sebuah indeks, misalnya di mulai dari file, yang menunjukkan posisi di mana garis mulai. Anda bisa menggunakan format database, atau hanya menambahkan tabel pada awal file. Atau buat yang terpisah file indeks untuk menemani anda besar file teks.
misalnya, anda mungkin membuat sebuah daftar dari karakter posisi untuk baris baru:
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
kemudian baca dengan ekor
yang sebenarnya `mencari langsung ke titik yang tepat dalam file!
misalnya untuk mendapatkan garis 1000:
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
Sebagai tindak lanjut untuk CaffeineConnoisseur's sangat membantu benchmarking menjawab... saya ingin tahu seberapa cepat 'mapfile' metode ini dibandingkan dengan orang lain (seperti itu't diuji), jadi saya mencoba cepat-dan-kotor perbandingan kecepatan sendiri seperti yang saya lakukan telah bash 4 berguna. Melemparkan dalam uji "ekor | head" metode (bukan kepala | ekor) yang disebutkan dalam salah satu komentar di atas jawaban sementara aku di itu, sebagai orang-orang menyanyikan pujian. Saya don't memiliki sesuatu yang hampir ukuran data test yang digunakan; yang terbaik yang saya bisa menemukan di pemberitahuan singkat adalah 14M silsilah file (garis panjang yang kosong dipisahkan, hanya 12000 di bawah garis).
Versi pendek: mapfile muncul lebih cepat dari metode cut, tapi lebih lambat dari segala sesuatu yang lain, jadi saya'd sebut itu tak berguna. ekor | kepala, OTOH, sepertinya bisa menjadi yang tercepat, meskipun dengan file ini ukuran perbedaan adalah tidak semua yang substansial 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
Harap ini membantu!
Menggunakan apa yang orang lain sebutkan, saya ingin ini menjadi sebuah cepat & dandy fungsi dalam bash shell.
Buat file: ~/.fungsi
Menambahkan untuk itu isi:
getline() { line=$1 sed $line'q;d' $2 }
Kemudian tambahkan ini untuk anda ~/.bash_profile
:
source ~/.fungsi
Sekarang ketika anda membuka baru bash jendela, anda hanya dapat memanggil fungsi yang telah jadi:
getline 441 myfile.txt
Banyak jawaban yang baik sudah. Saya secara pribadi pergi dengan awk. Untuk kenyamanan anda, jika anda menggunakan bash, hanya menambahkan di bawah ini untuk anda ~/.bash_profile
. Dan, waktu berikutnya anda log in (atau jika anda sumber anda .bash_profile setelah pembaruan ini), anda akan memiliki baru bagus "n" fungsi yang tersedia untuk pipa file anda melalui.
Eksekusi ini atau memasukkannya ke dalam file ~/.bash_profile (jika menggunakan bash) dan membuka kembali bash (atau mengeksekusi source ~/.bach_profile
)
# cetak hanya nth disalurkan di line n () { awk -vlnum=${1} 'NR==lnum {cetak; exit}'; }
Kemudian, untuk menggunakannya, cukup melalui pipa itu. E. g.,:
$ ya line | cat -n | n 5 5 line
I've menempatkan beberapa jawaban atas menjadi pendek bash script yang anda dapat dimasukkan ke dalam sebuah file bernama get.sh
dan link ke /usr/local/bin/mendapatkan
(atau apapun nama lain yang anda sukai).
#!/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
Memastikan itu's eksekusi dengan
$ chmod +x get
Link ini untuk membuatnya tersedia di JALAN
dengan
$ ln -s get.sh /usr/local/bin/get
Menikmati bertanggung jawab!
P