Bagaimana cara mendapatkan bagian dari file setelah baris pertama yang cocok dengan ekspresi reguler?


169

Saya punya file dengan sekitar 1000 baris. Saya ingin bagian file saya setelah baris yang cocok dengan pernyataan grep saya.

Itu adalah:

$ cat file | grep 'TERMINATE'     # It is found on line 534

Jadi, saya ingin file dari baris 535 ke baris 1000 untuk diproses lebih lanjut.

Bagaimana saya bisa melakukan itu?


34
UUOC (Penggunaan Berguna kucing):grep 'TERMINATE' file
Yakub

30
Saya tahu itu, seperti saya menggunakannya. Mari kita kembali ke pertanyaan.
Yugal Jindle

3
Ini adalah pertanyaan pemrograman yang sangat bagus, dan cocok untuk stackoverflow.
aioobe

13
@ Jacob Ini bukan penggunaan kucing yang tidak berguna sama sekali. Penggunaannya adalah untuk mencetak file ke output standar, yang berarti kita dapat menggunakan grepantarmuka input standar untuk membaca data, daripada harus mempelajari saklar apa yang berlaku untuk grep, dan sed, dan awk, dan pandoc, dan ffmpeglain - lain ketika kita ingin membaca dari file. Menghemat waktu karena kita tidak harus belajar peralihan baru setiap kali kita ingin melakukan hal yang sama: membaca dari file.
runeks

@runeks Saya setuju dengan sentimen Anda - tetapi Anda dapat mencapai itu tanpa cat: grep 'TERMINATE' < file. Mungkin itu memang membuat bacaan sedikit lebih sulit - tapi ini adalah shell scripting, jadi itu akan selalu menjadi masalah :)
LOAS

Jawaban:


307

Berikut ini akan mencetak baris yang cocok TERMINATEsampai akhir file:

sed -n -e '/TERMINATE/,$p'

Dijelaskan: -n menonaktifkan perilaku default sedpencetakan setiap baris setelah mengeksekusi skripnya di atasnya, -emenunjukkan skrip untuk sed, /TERMINATE/,$adalah pilihan kisaran alamat (baris) yang berarti baris pertama yang cocok dengan TERMINATEekspresi reguler (seperti grep) ke akhir file ( $) , dan pmerupakan perintah cetak yang mencetak baris saat ini.

Ini akan mencetak dari baris yang mengikuti baris yang cocok TERMINATEsampai akhir file:
(dari SETELAH baris yang cocok ke EOF, BUKAN termasuk baris yang cocok)

sed -e '1,/TERMINATE/d'

Dijelaskan: 1,/TERMINATE/ adalah pilihan rentang alamat (baris) yang berarti baris pertama untuk input ke baris 1 yang cocok dengan TERMINATEekspresi reguler, dan dmerupakan perintah hapus yang menghapus baris saat ini dan melompat ke baris berikutnya. Karena sedperilaku default adalah untuk mencetak garis, itu akan mencetak garis setelah TERMINATE ke akhir input.

Edit:

Jika Anda ingin garis sebelumnya TERMINATE:

sed -e '/TERMINATE/,$d'

Dan jika Anda ingin kedua baris sebelum dan sesudah TERMINATEdalam 2 file berbeda dalam satu pass:

sed -e '1,/TERMINATE/w before
/TERMINATE/,$w after' file

File sebelum dan sesudah akan berisi baris dengan terminate, jadi untuk memproses setiap yang Anda butuhkan:

head -n -1 before
tail -n +2 after

Sunting2:

JIKA Anda tidak ingin membuat kode nama file dalam skrip sed, Anda dapat:

before=before.txt
after=after.txt
sed -e "1,/TERMINATE/w $before
/TERMINATE/,\$w $after" file

Tapi kemudian Anda harus melarikan diri dari $makna baris terakhir sehingga shell tidak akan mencoba untuk memperluas $wvariabel (perhatikan bahwa kami sekarang menggunakan tanda kutip ganda di sekitar skrip alih-alih tanda kutip tunggal).

Saya lupa mengatakan bahwa baris baru penting setelah nama file dalam skrip sehingga mereka tahu bahwa nama file berakhir.


Edit: 2016-0530

Sébastien Clément bertanya: "Bagaimana Anda mengganti hardcoded TERMINATEdengan variabel?"

Anda akan membuat variabel untuk teks yang cocok dan kemudian melakukannya dengan cara yang sama seperti contoh sebelumnya:

matchtext=TERMINATE
before=before.txt
after=after.txt
sed -e "1,/$matchtext/w $before
/$matchtext/,\$w $after" file

untuk menggunakan variabel untuk teks yang cocok dengan contoh sebelumnya:

## Print the line containing the matching text, till the end of the file:
## (from the matching line to EOF, including the matching line)
matchtext=TERMINATE
sed -n -e "/$matchtext/,\$p"
## Print from the line that follows the line containing the 
## matching text, till the end of the file:
## (from AFTER the matching line to EOF, NOT including the matching line)
matchtext=TERMINATE
sed -e "1,/$matchtext/d"
## Print all the lines before the line containing the matching text:
## (from line-1 to BEFORE the matching line, NOT including the matching line)
matchtext=TERMINATE
sed -e "/$matchtext/,\$d"

Poin penting tentang mengganti teks dengan variabel dalam kasus ini adalah:

  1. Variabel ( $variablename) yang disertakan dalam single quotes[ '] tidak akan "meluas" tetapi variabel di dalam double quotes[ "] akan. Jadi, Anda harus mengubah semua single quotesuntuk double quotesjika mereka mengandung teks yang ingin Anda ganti dengan variabel.
  2. The sedberkisar juga mengandung $dan segera diikuti oleh surat seperti: $p, $d, $w. Mereka juga akan terlihat seperti variabel yang akan diperluas, sehingga Anda harus melarikan diri mereka $karakter dengan garis miring terbalik [ \] seperti: \$p, \$d, \$w.

Bagaimana kita bisa mendapatkan baris sebelum TERMINASI dan menghapus semua yang mengikuti?
Yugal Jindle

Bagaimana Anda akan mengganti TERMINAL dengan hardcoded dengan variabel?
Sébastien Clément

2
Satu kasus penggunaan yang hilang di sini adalah cara mencetak garis setelah penanda terakhir (jika ada beberapa dari mereka dalam file .. think file log dll).
mato

Contoh sed -e "1,/$matchtext/d"tidak berfungsi ketika $matchtextmuncul di baris pertama. Saya harus mengubahnya ke sed -e "0,/$matchtext/d".
Karalga

61

Sebagai perkiraan sederhana yang dapat Anda gunakan

grep -A100000 TERMINATE file

yang menangkap TERMINATEdan menghasilkan hingga 100000 baris mengikuti garis itu.

Dari halaman manual

-A NUM, --after-context=NUM

Cetak NUM garis konteks tertinggal setelah garis yang cocok. Tempatkan garis yang berisi pemisah grup (-) di antara grup yang cocok dengan pertandingan. Dengan opsi -o atau --only-matching, ini tidak berpengaruh dan peringatan diberikan.


Itu mungkin bekerja untuk ini, tapi saya perlu kode ke dalam skrip saya untuk memproses banyak file. Jadi, tunjukkan beberapa solusi umum.
Yugal Jindle

3
Saya pikir ini adalah salah satu solusi praktis!
michelgotta

2
sama -B NUM, --before-context = NUM ​​Cetak NUM baris konteks terkemuka sebelum mencocokkan garis. Tempatkan garis yang berisi pemisah grup (-) di antara grup yang cocok dengan pertandingan. Dengan opsi -o atau --only-matching, ini tidak berpengaruh dan peringatan diberikan.
PiyusG

solusi ini bekerja untuk saya karena saya dapat dengan mudah menggunakan variabel sebagai string saya untuk memeriksa.
Jose Martinez

3
Ide bagus! Jika Anda tidak yakin tentang ukuran konteksnya, Anda dapat menghitung garis-garisnya filesebagai gantinya:grep -A$(cat file | wc -l) TERMINATE file
Lemming

26

Alat yang digunakan di sini awk:

cat file | awk 'BEGIN{ found=0} /TERMINATE/{found=1}  {if (found) print }'

Bagaimana cara kerjanya:

  1. Kami menetapkan variabel 'ditemukan' ke nol, mengevaluasi false
  2. jika kecocokan untuk 'TERMINATE' ditemukan dengan ekspresi reguler, kami setel ke satu.
  3. Jika variabel 'found' kami mengevaluasi ke True, cetak :)

Solusi lain mungkin menghabiskan banyak memori jika Anda menggunakannya pada file yang sangat besar.


Sederhana, elegan, dan sangat generik. Dalam kasus saya itu mencetak semuanya hingga kemunculan kedua '###':cat file | awk 'BEGIN{ found=0} /###/{found=found+1} {if (found<2) print }'
Aleksander Stelmaczonek

3
Alat yang tidak digunakan di sini adalah cat. awksangat mampu mengambil satu atau lebih nama file sebagai argumen. Lihat juga stackoverflow.com/questions/11710552/useless-use-of-cat
tripleee

9

Jika saya memahami pertanyaan Anda dengan benar Anda ingin garis setelah TERMINATE , tidak termasuk TERMINATE-line. awkdapat melakukan ini dengan cara sederhana:

awk '{if(found) print} /TERMINATE/{found=1}' your_file

Penjelasan:

  1. Meskipun bukan praktik terbaik, Anda bisa mengandalkan fakta bahwa semua vars default ke 0 atau string kosong jika tidak ditentukan. Jadi ungkapan pertama ( if(found) print) tidak akan mencetak apa pun untuk memulai.
  2. Setelah pencetakan selesai, kami memeriksa apakah ini adalah starter-line (yang seharusnya tidak dimasukkan).

Ini akan mencetak semua baris setelah itu TERMINATE-line.


Generalisasi:

  • Anda memiliki file dengan start - dan akhir -lines dan Anda ingin garis antara garis-garis tidak termasuk dalam awal - dan akhir -lines.
  • mulai - dan garis akhir dapat didefinisikan oleh ekspresi reguler yang cocok dengan garis.

Contoh:

$ cat ex_file.txt 
not this line
second line
START
A good line to include
And this line
Yep
END
Nope more
...
never ever
$ awk '/END/{found=0} {if(found) print} /START/{found=1}' ex_file.txt 
A good line to include
And this line
Yep
$

Penjelasan:

  1. Jika garis akhir ditemukan, pencetakan tidak boleh dilakukan. Perhatikan bahwa pemeriksaan ini dilakukan sebelum pencetakan yang sebenarnya untuk mengecualikan garis akhir dari hasilnya.
  2. Cetak baris saat ini jika founddiatur.
  3. Jika garis- start ditemukan maka atur found=1sehingga baris-baris berikut dicetak. Perhatikan bahwa pemeriksaan ini dilakukan setelah pencetakan aktual untuk mengecualikan garis start dari hasilnya.

Catatan:

  • Kode mengandalkan fakta bahwa semua awk-vars default ke 0 atau string kosong jika tidak didefinisikan. Ini valid tetapi mungkin bukan praktik terbaik sehingga Anda bisa menambahkan a BEGIN{found=0}ke awal ekspresi awk.
  • Jika beberapa awal-akhir- blok ditemukan mereka semua dicetak.

1
Luar Biasa Contoh Luar Biasa. Hanya menghabiskan 2 jam melihat csplit, sed, dan segala macam perintah awk yang rumit. Ini tidak hanya melakukan apa yang saya inginkan tetapi juga cukup sederhana untuk menyimpulkan bagaimana memodifikasinya untuk melakukan beberapa hal terkait lainnya yang saya butuhkan. Buat saya ingat awk itu hebat dan tidak hanya dalam kekacauan yang tak dapat dipahami. Terima kasih.
user1169420

{if(found) print}sedikit anti-pola dalam awk, itu lebih idiomatis untuk mengganti blok hanya dengan foundatau found;jika Anda memerlukan filter lain sesudahnya.
user000001

@ user000001 tolong jelaskan. Saya tidak mengerti apa yang harus diganti dan bagaimana caranya. Pokoknya saya pikir cara tulisannya membuatnya sangat jelas apa yang sedang terjadi.
UlfR

1
Anda akan menggantinya awk '{if(found) print} /TERMINATE/{found=1}' your_filedengan awk 'found; /TERMINATE/{found=1}' your_file, mereka berdua harus melakukan hal yang sama.
user000001

7

Gunakan ekspansi parameter bash seperti berikut:

content=$(cat file)
echo "${content#*TERMINATE}"

Bisakah Anda jelaskan apa yang Anda lakukan?
Yugal Jindle

Saya menyalin konten "file" ke variabel $ content. Kemudian saya menghapus semua karakter sampai "TERMINATE" terlihat. Itu tidak menggunakan pencocokan serakah, tetapi Anda dapat menggunakan pencocokan serakah dengan $ {content ## * TERMINATE}.
Mu Qiao

di sini adalah tautan manual bash: gnu.org/software/bash/manual/…
Mu Qiao

6
apa yang akan terjadi jika file berukuran 100GB?
Znik

1
Downvote: Ini mengerikan (membaca file menjadi variabel) dan salah (menggunakan variabel tanpa mengutipnya; dan Anda harus menggunakan printfatau memastikan Anda tahu persis apa yang Anda sampaikan echo.).
tripleee

6

grep -Sebuah 10000000 'TERMINATE'

  • jauh, lebih cepat daripada sed terutama bekerja pada file yang sangat besar. Ia bekerja hingga 10 juta baris (atau apa pun yang Anda masukkan) sehingga tidak ada salahnya membuat ini cukup besar untuk menangani apa pun yang Anda tekan.

4

Ada banyak cara untuk melakukannya dengan sedatau awk:

sed -n '/TERMINATE/,$p' file

Ini terlihat TERMINATEdi file Anda dan dicetak dari baris itu hingga akhir file.

awk '/TERMINATE/,0' file

Ini persis perilaku yang sama dengan sed.

Jika Anda tahu jumlah baris dari mana Anda ingin mulai mencetak, Anda dapat menentukannya bersama NR(jumlah catatan, yang akhirnya menunjukkan jumlah baris):

awk 'NR>=535' file

Contoh

$ seq 10 > a        #generate a file with one number per line, from 1 to 10
$ sed -n '/7/,$p' a
7
8
9
10
$ awk '/7/,0' a
7
8
9
10
$ awk 'NR>=7' a
7
8
9
10

Untuk nomor Anda juga dapat menggunakanmore +7 file
123

Ini termasuk baris yang cocok, yang bukan yang diinginkan dalam pertanyaan ini.
mivk

@mivk yah, ini juga merupakan kasus dari jawaban yang diterima dan ke-2 yang paling banyak dipilih, jadi masalahnya mungkin dengan judul yang menyesatkan.
fedorqui 'SO berhenti merugikan'

3

Jika karena alasan apa pun, Anda ingin menghindari penggunaan sed, berikut ini akan mencetak baris yang cocok TERMINATEsampai akhir file:

tail -n "+$(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)" file

dan yang berikut ini akan dicetak dari baris berikut yang cocok TERMINATEsampai akhir file:

tail -n "+$(($(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)+1))" file

Dibutuhkan 2 proses untuk melakukan apa yang dapat dilakukan sed dalam satu proses, dan jika file berubah antara eksekusi grep dan tail, hasilnya bisa membingungkan, jadi saya sarankan menggunakan sed. Selain itu, jika file yang dikerjakan tidak mengandung TERMINATE, perintah 1 gagal.


file dipindai dua kali. bagaimana jika ukurannya 100GB?
Znik

1
Diturunkan karena ini adalah solusi yang jelek, tetapi kemudian dipilih karena 90% jawabannya adalah peringatan.
Fisikawan Gila


0

Ini bisa menjadi salah satu cara untuk melakukannya. Jika Anda tahu baris file apa yang Anda miliki kata grep Anda dan berapa banyak baris yang Anda miliki di file Anda:

grep -A466 'TERMINATE' file


1
Jika nomor baris diketahui, maka greptidak diperlukan; Anda bisa menggunakan tail -n $NUM, jadi ini bukan jawaban.
Samveen

-1

sed adalah alat yang jauh lebih baik untuk pekerjaan itu: file sed -n '/ re /, $ p'

dimana re adalah regexp.

Pilihan lain adalah flag --after-context grep. Anda harus memasukkan angka untuk mengakhiri, menggunakan wc pada file harus memberikan nilai yang tepat untuk berhenti. Kombinasikan ini dengan -n dan ekspresi pertandingan Anda.


--setelah-konteks baik-baik saja tetapi tidak dalam semua kasus.
Yugal Jindle

Bisakah Anda menyarankan sesuatu yang lain .. ??
Yugal Jindle

-2

Ini akan mencetak semua baris dari baris terakhir yang ditemukan "TERMINATE" hingga akhir file:

LINE_NUMBER=`grep -o -n TERMINATE $OSCAM_LOG|tail -n 1|sed "s/:/ \\'/g"|awk -F" " '{print $1}'`
tail -n +$LINE_NUMBER $YOUR_FILE_NAME

Mengekstrak nomor baris dengan grepsehingga Anda dapat memberinya makan tailadalah antipattern yang boros. Menemukan kecocokan dan mencetak melalui akhir file (atau, sebaliknya, mencetak dan menghentikan pada kecocokan pertama) secara nyata dilakukan dengan alat regex yang normal dan esensial itu sendiri. Yang masif grep | tail | sed | awkjuga dengan sendirinya merupakan penggunaan grepdan teman yang tidak berguna .
tripleee

Saya pikir dia mencoba memberi kita sesuatu yang akan menemukan / instance terakhir / dari 'TERMINATE' dan memberikan baris dari instance itu pada. Implementasi lain memberi Anda contoh pertama dan seterusnya. LINE_NUMBER mungkin akan terlihat seperti ini, sebagai gantinya: LINE_NUMBER = $ (grep -o -n 'TERMINATE' $ OSCAM_LOG | tail -n 1 | awk -F: '{print $ 1}') Mungkin bukan cara yang paling elegan, tetapi tampaknya menyelesaikan pekerjaan. ^. ^
fbicknel

... atau semuanya dalam satu baris, tetapi jelek: tail -n + $ (grep -o -n 'TERMINATE' $ YOUR_FILE_NAME | tail -n 1 | awk -F: '{print $ 1}') $ YOUR_FILE_NAME
fbicknel

.... dan saya akan kembali dan mengedit $ OSCAM_LOG sebagai pengganti $ YOUR_FILE_NAME ... tetapi tidak dapat karena suatu alasan. Tidak tahu dari mana $ OSCAM_LOG berasal; Saya hanya dengan nggak sengaja memburamkannya. oO
fbicknel

Melakukan ini di Awk saja adalah tugas umum dalam Awk 101. Jika Anda sudah menggunakan alat yang lebih mampu hanya untuk mendapatkan nomor baris, lepaskan taildan lakukan tugas di alat yang lebih mampu sekaligus. Lagi pula, judulnya dengan jelas mengatakan "pertandingan pertama".
tripleee
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.