Bagaimana saya bisa mengekstrak rentang garis yang telah ditentukan dari file teks di Unix?

532

Saya memiliki ~ 23000 baris SQL dump yang berisi beberapa data bernilai data. Saya perlu mengekstrak bagian tertentu dari file ini (yaitu data untuk database tunggal) dan menempatkannya di file baru. Saya tahu nomor awal dan akhir dari data yang saya inginkan.

Adakah yang tahu perintah Unix (atau serangkaian perintah) untuk mengekstrak semua baris dari file antara say line 16224 dan 16482 lalu mengarahkannya ke file baru?

Adam J. Forster
sumber
Karena Anda menyebutkan file besar, saya sarankan memeriksa komentar stackoverflow.com/questions/83329/...
sancho.s ReinstateMonicaCellio

Jawaban:

793
sed -n '16224,16482p;16483q' filename > newfile

Dari manual sed :

p - Cetak ruang pola (ke output standar). Perintah ini biasanya hanya digunakan bersama dengan opsi -n command-line.

n - Jika cetak-otomatis tidak dinonaktifkan, cetak ruang pola, kemudian, ganti ruang pola dengan baris input berikutnya. Jika tidak ada lagi input maka sed keluar tanpa memproses perintah lagi.

q - Keluar sedtanpa memproses perintah atau input lagi. Perhatikan bahwa ruang pola saat ini dicetak jika cetak-otomatis tidak dinonaktifkan dengan opsi -n.

dan

Alamat dalam skrip sed dapat berupa salah satu dari bentuk berikut:

number Menentukan nomor baris hanya akan cocok dengan baris itu di input.

Rentang alamat dapat ditentukan dengan menetapkan dua alamat yang dipisahkan oleh koma (,). Rentang alamat cocok dengan garis mulai dari tempat alamat pertama cocok, dan berlanjut hingga alamat kedua cocok (secara inklusif).

boxxar
sumber
3
Saya ingin tahu apakah ini memodifikasi file asli. Saya mencadangkannya untuk berjaga-jaga dan tampaknya ini TIDAK memodifikasi yang asli, seperti yang diharapkan.
Andy Groff
@AndyGroff. Untuk memodifikasi file di tempat, gunakan parameter "-i". Kalau tidak, itu tidak akan mengubah file.
youri
175
Jika, seperti saya, Anda perlu melakukan ini pada file SANGAT besar, akan membantu jika Anda menambahkan perintah berhenti di baris berikutnya. Lalu itu sed -n '16224,16482p;16483q' filename. Kalau tidak, sed akan terus memindai sampai akhir (atau setidaknya versi saya lakukan).
wds
7
@MilesRout orang-orang sepertinya bertanya "mengapa downvote?" cukup sering, mungkin maksud Anda "Saya tidak peduli" alih-alih "tidak ada yang peduli"
Mark
1
@wds - Komentar Anda layak mendapatkan jawaban yang naik ke atas. Itu bisa membuat perbedaan antara siang dan malam.
sancho.s ReinstateMonicaCellio
203
sed -n '16224,16482 p' orig-data-file > new-file

Di mana 16224.16482 adalah nomor baris awal dan nomor baris akhir, inklusif. Ini 1-diindeks. -nmenekan gema input sebagai output, yang Anda jelas tidak inginkan; angka menunjukkan rentang garis untuk membuat perintah berikut beroperasi; perintah pmencetak garis yang relevan.

JXG
sumber
7
Pada file besar, perintah di atas akan melanjutkan menayangkan seluruh file setelah rentang yang diinginkan ditemukan. Apakah ada cara untuk menghentikan pemrosesan file setelah rentang telah di-output?
Gary
39
Nah, dari jawabannya di sini , tampaknya bahwa berhenti di akhir rentang dapat dicapai dengan: sed -n '16224,16482p;16482q' orig-data-file > new-file.
Gary
5
Mengapa Anda memasukkan ruang yang tidak perlu, dan kemudian harus mengutip? (Tentu saja, membuat masalah yang tidak perlu dan menyelesaikannya adalah inti dari setengah dari ilmu komputer, tapi maksud saya di samping alasan itu ...)
Kaz
92

Cukup sederhana menggunakan kepala / ekor:

head -16482 in.sql | tail -258 > out.sql

menggunakan sed:

sed -n '16482,16482p' in.sql > out.sql

menggunakan awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql
manveru
sumber
1
Opsi kedua dan ketiga OK, tetapi yang pertama lebih lambat daripada banyak alternatif karena menggunakan 2 perintah di mana 1 cukup. Ini juga membutuhkan perhitungan untuk mendapatkan argumen yang tepat tail.
Jonathan Leffler
3
Perlu dicatat bahwa untuk menjaga nomor baris yang sama dengan pertanyaan, perintah sed harus sed -n 16224,16482p' in.sql >out.sqldan perintah awk harusawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz
3
Juga patut diketahui bahwa dalam kasus contoh pertama head -16482 in.sql | tail -$((16482-16224)) >out.sqlmeninggalkan perhitungan ke bash
sibaz
1
Yang pertama dengan kepala dan ekor WAYYYY lebih cepat pada file besar daripada versi sed, bahkan dengan opsi q ditambahkan. versi kepala instan dan versi sed I Ctrl-C setelah satu menit ... Terima kasih
Miyagi
2
Bisa juga digunakan tail -n +16224untuk mengurangi perhitungan
SOFe
35

Anda bisa menggunakan 'vi' dan kemudian perintah berikut:

:16224,16482w!/tmp/some-file

Kalau tidak:

cat file | head -n 16482 | tail -n 258

EDIT: - Hanya untuk menambahkan penjelasan, Anda menggunakan head -n 16482 untuk menampilkan 16482 baris pertama kemudian gunakan tail -n 258 untuk mendapatkan 258 baris terakhir dari output pertama.

Mark Janssen
sumber
2
Dan alih-alih vi Anda bisa menggunakan ex, itu adalah vi minus konsol interaktif.
Tadeusz A. Kadłubowski
1
Anda tidak membutuhkan catperintah; headdapat membaca file secara langsung. Ini lebih lambat daripada banyak alternatif karena menggunakan 2 (3 seperti yang ditunjukkan) perintah di mana 1 sudah cukup.
Jonathan Leffler
1
@ JonathanLeffler Anda salah besar. Ini sangat cepat. Saya mengekstrak 200k baris, sekitar 1G, dari file 2G dengan 500k baris, dalam beberapa detik (tanpa cat). Solusi lain memerlukan setidaknya beberapa menit. Juga variasi tercepat pada GNU tampaknya tail -n +XXX filename | head XXX.
Antonis Christofides
28

Ada pendekatan lain dengan awk:

awk 'NR==16224, NR==16482' file

Jika file tersebut berukuran besar, sebaiknya exitsetelah membaca baris yang diinginkan terakhir. Dengan cara ini, tidak perlu membaca baris-baris berikut secara tidak perlu:

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file
fedorqui 'SO berhenti merugikan'
sumber
2
1+ untuk menghemat runtime dan sumber daya dengan menggunakan print; exit. Terima kasih!
Bernie Reiter
Penyederhanaan sedikit contoh 2:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade
Itu cerah, terima kasih @ RobinA.Meade! Saya mengedit ide Anda di post
fedorqui 'SO stop harming'
17
perl -ne 'print if 16224..16482' file.txt > new_file.txt
mmaibaum
sumber
9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2
Cetra
sumber
6
cat dump.txt | head -16224 | tail -258

harus melakukan trik. Kelemahan dari pendekatan ini adalah bahwa Anda perlu melakukan aritmatika untuk menentukan argumen untuk tail dan untuk memperhitungkan apakah Anda ingin 'antara' menyertakan garis akhir atau tidak.

JP Lodine
sumber
4
Anda tidak membutuhkan catperintah; headdapat membaca file secara langsung. Ini lebih lambat daripada banyak alternatif karena menggunakan 2 (3 seperti yang ditunjukkan) perintah di mana 1 sudah cukup.
Jonathan Leffler
@JonathanLeffler Jawaban ini paling mudah dibaca dan diingat. Jika Anda benar-benar peduli dengan kinerja Anda tidak akan menggunakan shell di tempat pertama. Ini adalah praktik yang baik untuk membiarkan alat khusus mendedikasikan diri pada tugas tertentu. Selanjutnya, "aritmatika" dapat diselesaikan menggunakan | tail -$((16482 - 16224)).
Yeti
6

Berdiri di pundak boxxar, saya suka ini:

sed -n '<first line>,$p;<last line>q' input

misalnya

sed -n '16224,$p;16482q' input

The $berarti "baris terakhir", sehingga perintah pertama membuat sedmencetak semua baris yang dimulai dengan garis 16224dan merek perintah kedua sedberhenti setelah mencetak baris 16428. (Menambahkan 1untuk q-range dalam solusi boxxar tampaknya tidak diperlukan.)

Saya suka varian ini karena saya tidak perlu menentukan nomor baris akhir dua kali. Dan saya mengukur bahwa menggunakan $tidak memiliki efek buruk pada kinerja.

Tilman Vogel
sumber
5

sed -n '16224,16482p' < dump.sql

kubus
sumber
3

Cepat dan kotor:

head -16428 < file.in | tail -259 > file.out

Mungkin bukan cara terbaik untuk melakukannya tetapi harus berhasil.

BTW: 259 = 16482-16224 + 1.

jan.vdbergh
sumber
Ini lebih lambat daripada banyak alternatif karena menggunakan 2 perintah di mana 1 cukup.
Jonathan Leffler
3

Saya menulis sebuah program Haskell bernama splitter yang melakukan hal ini: membaca melalui posting blog rilis saya .

Anda dapat menggunakan program ini sebagai berikut:

$ cat somefile | splitter 16224-16482

Dan hanya itu yang ada di sana. Anda akan membutuhkan Haskell untuk menginstalnya. Hanya:

$ cabal install splitter

Dan kamu sudah selesai. Saya harap Anda menemukan program ini bermanfaat.

Robert Massaioli
sumber
Apakah splitterhanya membaca dari input standar? Dalam arti tertentu, itu tidak masalah; yang catperintah berlebihan apakah itu dilakukan atau tidak. Baik menggunakan splitter 16224-16482 < somefileatau (jika dibutuhkan argumen nama file) splitter 16224-16482 somefile.
Jonathan Leffler
3

Bahkan kita dapat melakukan ini untuk memeriksa di baris perintah:

cat filename|sed 'n1,n2!d' > abc.txt

Sebagai contoh:

cat foo.pl|sed '100,200!d' > abc.txt
Chinmoy Padhi
sumber
6
Anda tidak memerlukan catperintah di salah satu dari ini; sedsangat mampu membaca file sendiri, atau Anda dapat mengarahkan input standar dari file.
Jonathan Leffler
3

Menggunakan ruby:

ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf
Carl Blakeley
sumber
2

Saya baru akan memposting trik kepala / ekor, tetapi sebenarnya saya mungkin baru saja menjalankan emacs. ;-)

  1. esc- xgoto-line ret16224
  2. tandai ( ctrl- space)
  3. esc- xgoto-line ret16482
  4. esc-w

buka file output baru, simpan ctl-y

Mari saya lihat apa yang terjadi.

sammyo
sumber
4
Emacs tidak bekerja dengan sangat baik pada file yang sangat besar menurut pengalaman saya.
Greg Mattes
Bisakah Anda menjalankannya sebagai tindakan yang dituliskan skrip, atau hanya opsi interaktif?
Jonathan Leffler
2

Saya akan menggunakan:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR berisi nomor catatan (baris) dari baris yang sedang dibaca dari file.

Paddy3118
sumber
2

Saya ingin melakukan hal yang sama dari skrip menggunakan variabel dan mencapainya dengan meletakkan tanda kutip di sekitar $ variabel untuk memisahkan nama variabel dari p:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

Saya ingin membagi daftar menjadi folder yang terpisah dan menemukan pertanyaan awal dan menjawab langkah yang bermanfaat. (perintah split bukan opsi pada os lama saya harus port kode ke).

KevinY
sumber
1

Saya menulis skrip bash kecil yang dapat Anda jalankan dari baris perintah Anda, asalkan Anda memperbarui PATH Anda untuk memasukkan direktori (atau Anda dapat menempatkannya di direktori yang sudah terkandung dalam PATH).

Penggunaan: $ pinch filename start-line end-line

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0
DrNerdfighter
sumber
1
Ini lebih lambat daripada banyak alternatif karena menggunakan 2 perintah di mana 1 cukup. Bahkan, ia membaca file dua kali karena wcperintah, yang membuang-buang bandwidth disk, terutama pada file gigabyte. Dalam segala macam cara, ini didokumentasikan dengan baik, tetapi juga rekayasa berlebihan.
Jonathan Leffler
1

Ini mungkin bekerja untuk Anda (sed GNU):

sed -ne '16224,16482w newfile' -e '16482q' file

atau memanfaatkan bash:

sed -n $'16224,16482w newfile\n16482q' file
potong
sumber
1

Menggunakan ed:

ed -s infile <<<'16224,16482p'

-smenekan keluaran diagnostik; perintah sebenarnya ada di sini-string. Secara khusus, 16224,16482pjalankan perintah p(cetak) pada kisaran alamat jalur yang diinginkan.

Benjamin W.
sumber
0

-N pada jawaban terima berfungsi. Berikut cara lain jika Anda ingin.

cat $filename | sed "${linenum}p;d";

Ini melakukan hal berikut:

  1. pipa dalam isi file (atau umpan dalam teks sesuka Anda).
  2. sed memilih garis yang diberikan, mencetaknya
  3. d diharuskan untuk menghapus baris, jika tidak maka akan menganggap semua baris pada akhirnya akan dicetak. yaitu, tanpa d, Anda akan mendapatkan semua baris dicetak oleh baris yang dipilih dicetak dua kali karena Anda memiliki bagian $ {linenum} p yang memintanya untuk dicetak. Saya cukup yakin -n pada dasarnya melakukan hal yang sama dengan d di sini.
ThinkBonobo
sumber
3
catatan cat file | sedlebih baik ditulis sebagaised file
fedorqui 'SO stop harming'
Ini juga hanya mencetak garis, sedangkan pertanyaannya adalah tentang kisaran mereka.
fedorqui 'SO berhenti merugikan'
0

Karena kita berbicara tentang mengekstraksi baris teks dari file teks, saya akan memberikan kasus khusus di mana Anda ingin mengekstraksi semua baris yang cocok dengan pola tertentu.

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

Akan mencetak baris [Data] dan sisanya. Jika Anda ingin teks dari line1 ke pola, Anda mengetik: sed -n '1, / Data / p' myfile. Selanjutnya, jika Anda tahu dua pola (lebih baik menjadi unik dalam teks Anda), baik garis awal dan akhir rentang dapat ditentukan dengan kecocokan.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Kemin Zhou
sumber