Perintah untuk menampilkan beberapa baris pertama dan terakhir beberapa file

23

Saya punya file dengan banyak baris, dan setiap baris memiliki cap waktu di awal, seperti

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

Jadi, saya sering memeriksa 2 hal dari file log ini.

  1. Beberapa baris pertama, yang memiliki kondisi global dan waktu mulai juga diberikan.
  2. Beberapa baris terakhir, yang memiliki status keluar dengan beberapa info lainnya.

Apakah ada perintah tunggal praktis cepat yang dapat membuat saya menampilkan hanya beberapa baris pertama dan terakhir file?

mtk
sumber
2
Apa kondisi global, dan tidak head and tailberfungsi untuk Anda?
daisy
Itu adalah bagian dari file log saya. Saya berusaha menjadi elaboratif. Anda bisa mengabaikannya.
mtk
Solusi Anda terlihat baik bagi saya. Jika Anda ingin lebih nyaman, buatlah itu menjadi fungsi shell (bahkan alias juga bisa).
vonbrand
@vonbrand Masalahnya adalah saya tidak tahuN
Bernhard
@ Bernhard, saya bukan sed(1)ahli, tetapi ada cara menyimpan barang untuk digunakan nanti. Mungkin terbayar untuk melihat ke sana. OTOH, saya mungkin akan membuat skrip Perl (atau apa pun) untuk melakukannya jika sering digunakan, karena saya lebih akrab dengan itu.
vonbrand

Jawaban:

12

Anda dapat menggunakan sedatau awkmembuatnya dengan satu perintah. Namun Anda akan kehilangan kecepatan, sebab seddan bagaimanapun awkjuga harus menjalankan seluruh file. Dari sudut pandang kecepatan, lebih baik membuat fungsi atau setiap waktu untuk kombinasi tail+ head. Ini memang memiliki kelemahan dari tidak bekerja jika input adalah pipa, namun Anda dapat menggunakan substitusi proses, jika shell Anda mendukungnya (lihat contoh di bawah).

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"
}

dan hanya meluncurkannya sebagai

first_last "/path/to/file_to_process"

untuk melanjutkan dengan substitusi proses (bash, zsh, ksh like shells saja):

first_last <( command )

ps. Anda bahkan dapat menambahkan grepuntuk memeriksa apakah "kondisi global" Anda ada.

buru-buru
sumber
-n 10apakah standarnya, bukan?
l0b0
@ l0b0 ya, ini default. -n 10tidak perlu di sini.
buru
20

@rush benar tentang menggunakan head + tail yang lebih efisien untuk file besar, tetapi untuk file kecil (<20 baris), beberapa baris mungkin di-output dua kali.

{ head; tail;} < /path/to/file

akan sama-sama efisien, tetapi tidak akan memiliki masalah di atas.

Stéphane Chazelas
sumber
Berbeda dengan solusi rush, ini tidak bekerja di shell POSIX.
Marco
2
@ Marsco Hah? Hanya konstruk POSIX yang digunakan di sini. Apa yang Anda lihat salah?
Gilles 'SANGAT berhenti menjadi jahat'
2
@Gilles Aku merindukan ruang: {head; tail;} < filebekerja di zsh tetapi gagal di sh. { head; tail;} < fileselalu berhasil. Maaf atas kebisingannya.
Marco
@ Marsco, jika ada masalah dengan itu, itu akan terjadi head, bukan shell. POSIX headharus membiarkan kursor dalam file melewati 10 baris untuk file biasa. Masalah dapat muncul untuk headimplementasi non-POSIX (versi yang sangat lama dari GNU head dulunya tidak sesuai dalam hal itu, tapi kami berbicara beberapa dekade) atau jika file tidak dapat dicari (seperti pipa atau soket yang dinamai, tetapi kemudian solusi lain akan memiliki masalah yang sama).
Stéphane Chazelas
1
@FCTW,sudo sh -c '{ head; tail;} < /path/to/file'
Stéphane Chazelas
9

The { head; tail; }solusi tidak akan bekerja pada pipa (atau soket atau file non-seekable lainnya) karena headbisa mengkonsumsi terlalu banyak data seperti membaca dengan blok dan tidak dapat mencari kembali pada pipa berpotensi meninggalkan kursor di dalam file melampaui apa tailyang dimaksud memilih.

Jadi, Anda bisa menggunakan alat yang membaca satu karakter pada waktu seperti shell read(di sini menggunakan fungsi yang mengambil jumlah garis kepala dan garis ekor sebagai argumen).

head_tail() {
  n=0
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  done
  tail -n "${2-$1}"
}
seq 100 | head_tail 5 10
seq 20 | head_tail 5

atau mengimplementasikan tailawk misalnya sebagai:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
    {l[NR%t]=$0}
    NR<=h
    END{
      n=NR-t+1
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]
    }'
}

Dengan sed:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'
}

(walaupun berhati-hatilah bahwa beberapa sedimplementasi memiliki batasan rendah pada ukuran ruang pola mereka, sehingga akan gagal untuk nilai besar dari jumlah garis ekor).

Stéphane Chazelas
sumber
4

Menggunakan bashsubstitusi proses, Anda dapat melakukan hal berikut:

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

Perhatikan bahwa garis tidak dijamin dalam urutan, meskipun untuk file yang lebih panjang dari sekitar 8 kB, kemungkinan besar akan. Cutoff 8kB ini adalah ukuran khas buffer baca, dan terkait dengan alasan | {head; tail;}tidak bekerja untuk file kecil.

The cat >/dev/nulldiperlukan untuk menjaga headpipa hidup. Kalau tidak, teeakan keluar lebih awal, dan sementara Anda akan mendapatkan output tail, itu akan berasal dari suatu tempat di tengah input, bukan akhir.

Akhirnya, mengapa >/dev/nullalih - alih, katakanlah, pindah tailke yang lain |? Dalam kasus berikut:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

headstdout dimasukkan ke dalam pipa taildaripada konsol, yang bukan apa yang kita inginkan sama sekali.

Jander
sumber
Ketika kepala atau ekor selesai menulis output yang mereka inginkan, mereka menutup stdin dan keluar. Dari situlah SIGPIPE berasal. Biasanya ini adalah hal yang baik, mereka membuang sisa output, jadi tidak ada alasan bagi pihak lain untuk terus menghabiskan waktu untuk menghasilkannya.
derobert
Apa yang membuat pesanan cenderung ditegakkan? Mungkin untuk file besar, karena tailharus bekerja lebih lama, tetapi saya berharap (dan memang melihat) gagal sekitar setengah waktu untuk input pendek.
Gilles 'SO- stop being evil'
Anda akan mendapatkan SIGPIPE dengan tee >(head) >(tail)alasan yang sama ( >(...)yang merupakan fitur ksh yang sekarang didukung oleh zsh dan bash juga) menggunakan pipa juga. Anda bisa melakukannya ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null)tetapi Anda masih akan melihat beberapa pesan kesalahan pipa yang rusaktee .
Stéphane Chazelas
Pada sistem saya (bash 4.2.37, coreutils 8.13), tailadalah orang yang dibunuh oleh SIGPIPE, bukan tee, dan tailtidak menulis ke sebuah pipa. Jadi itu pasti dari kill(), kan? Dan ini hanya terjadi ketika saya menggunakan |sintaks. stracemengatakan bahwa teeitu tidak menelepon kill()... jadi mungkin bash?
Jander
1
@Jander, coba beri makan lebih dari 8k sepertiseq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
Stéphane Chazelas
3

Menggunakan ed(yang akan membaca seluruh file ke dalam RAM):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file
curx
sumber
Lebih pendek:ed -s file <<< $'11,$-10d\n,p\nq\n'
don_crissti
2

Solusi pertama Stephane dalam fungsi sehingga Anda dapat menggunakan argumen (berfungsi di shell Bourne-like atau POSIX):

head_tail() {
    head "$@";
    tail "$@";
}

Sekarang Anda bisa melakukan ini:

head_tail -n 5 < /path/to/file

Ini tentu saja mengasumsikan bahwa Anda hanya melihat satu file dan seperti solusi Stephane bekerja (andal) hanya pada file biasa (yang dapat dicari).

l0b0
sumber
2

Dengan opsi -u( --unbuffered) dari GNU sed, Anda dapat menggunakan sed -u 2qsebagai alternatif tanpa buffer untuk head -n2:

$ seq 100|(sed -u 2q;tail -n2)
1
2
99
100

(head -n2;tail -n2)gagal ketika baris terakhir adalah bagian dari blok input yang dikonsumsi oleh head:

$ seq 1000|(head -n2;tail -n2)
1
2
999
1000
$ seq 100|(head -n2;tail -n2)
1
2
nisetama
sumber
ini harus menjadi jawaban teratas! bekerja seperti pesona!
Ben Usman
1

Saya bertemu dengan sesuatu seperti ini hari ini di mana saya hanya membutuhkan baris terakhir dan beberapa baris dari depan sungai dan muncul dengan yang berikut.

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

Saya membaca ini sebagai: menginisialisasi ruang penahanan dengan isi baris pertama, menambahkan baris 2-3 di ruang penahanan, di EOF menambahkan baris terakhir ke ruang penahanan, menukar ruang penahanan dan pola, dan mencetak pola ruang.

Mungkin seseorang dengan lebih banyak- sedfu daripada yang saya dapat mengetahui bagaimana menggeneralisasi ini untuk mencetak beberapa baris terakhir dari aliran yang ditunjukkan dalam pertanyaan ini, tetapi saya tidak membutuhkannya dan tidak dapat menemukan cara mudah untuk melakukan matematika berdasarkan $alamat dalam sedatau mungkin dengan mengelola ruang pegang sehingga hanya beberapa baris terakhir yang ada di dalamnya ketika EOFtercapai.

bocor
sumber
1

Anda dapat mencoba Perl, jika Anda telah menginstalnya:

perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

Ini akan berfungsi untuk sebagian besar file, tetapi membaca seluruh file ke dalam memori sebelum memprosesnya. Jika Anda tidak terbiasa dengan irisan Perl, "0" dalam tanda kurung siku berarti "ambil baris pertama", dan "-3 ...- 1" berarti "ambil tiga baris terakhir". Anda dapat menyesuaikan keduanya untuk kebutuhan Anda. Jika Anda perlu memproses file yang sangat besar (apa yang 'besar' mungkin tergantung pada RAM Anda dan mungkin ukuran swap), Anda mungkin ingin:

perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

mungkin agak lambat, karena membuat irisan setiap iterasi, tapi itu tergantung pada ukuran file.

Kedua perintah harus bekerja baik dalam pipa dan dengan file biasa.

Jasio
sumber