Bagaimana cara menghapus baris pertama file teks menggunakan skrip bash / sed?

555

Saya perlu berulang kali menghapus baris pertama dari file teks besar menggunakan skrip bash.

Saat ini saya menggunakan sed -i -e "1d" $FILE- tetapi butuh sekitar satu menit untuk melakukan penghapusan.

Apakah ada cara yang lebih efisien untuk mencapai ini?

Brent
sumber
untuk apa aku berdiri?
cikatomo
4
@cikatomo: singkatan dari inline edit - mengedit file dengan apa pun yang Anda hasilkan.
drewrockshard
4
ekor jauh lebih lambat dari sed. ekor membutuhkan 13,5 detik, sed membutuhkan 0,85 detik. File saya memiliki ~ 1M baris, ~ 100MB. MacBook Air 2013 dengan SSD.
jcsahnwaldt mengatakan GoFundMonica

Jawaban:

1030

Coba ekor :

tail -n +2 "$FILE"

-n x: Cukup cetak xbaris terakhir . tail -n 5akan memberi Anda 5 baris terakhir dari input. The +tanda jenis membalikkan argumen dan make tailapapun cetak tetapi yang pertama x-1garis. tail -n +1akan mencetak seluruh file, tail -n +2semuanya kecuali baris pertama, dll.

GNU tailjauh lebih cepat daripada sed. tailjuga tersedia di BSD dan -n +2bendera konsisten di kedua alat. Periksa halaman manual FreeBSD atau OS X untuk informasi lebih lanjut.

Versi BSD bisa lebih lambat dari itu sed. Saya bertanya-tanya bagaimana mereka mengaturnya; tailseharusnya hanya membaca file baris demi baris sementara sedmelakukan operasi yang cukup kompleks yang melibatkan menafsirkan skrip, menerapkan ekspresi reguler dan sejenisnya.

Catatan: Anda mungkin tergoda untuk menggunakannya

# THIS WILL GIVE YOU AN EMPTY FILE!
tail -n +2 "$FILE" > "$FILE"

tetapi ini akan memberi Anda file kosong . Alasannya adalah bahwa pengalihan ( >) terjadi sebelum taildipanggil oleh shell:

  1. Shell memotong file $FILE
  2. Shell menciptakan proses baru untuk tail
  3. Shell mengalihkan stdout dari tailproses ke$FILE
  4. tail membaca dari sekarang kosong $FILE

Jika Anda ingin menghapus baris pertama di dalam file, Anda harus menggunakan:

tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"

The &&akan memastikan bahwa file tidak ditimpa ketika ada masalah.

Aaron Digulla
sumber
3
Menurut ss64.com/bash/tail.html ini , standar buffer menjadi 32k saat menggunakan BSD 'tail' dengan -ropsi. Mungkin ada pengaturan buffer di suatu tempat di sistem? Atau -nnomor 32-bit yang ditandatangani?
Yzmir Ramirez
41
@ Eddie: user869097 mengatakan itu tidak berfungsi ketika satu baris 15Mb atau lebih. Selama garis lebih pendek, tailakan bekerja untuk ukuran file apa pun.
Aaron Digulla
6
dapatkah kamu menjelaskan argumen ini?
Dreampuf
17
@Dreampuf - dari halaman manual:-n N means output the last N lines, instead of the last 10; or use +N to output lines starting with the Nth
Will Sheppard
11
Saya akan setuju dengan @JonaChristopherSahnwaldt - ekor jauh, jauh lebih lambat daripada varian sed, dengan urutan besarnya. Saya mengujinya pada file 500.000 ribu baris (tidak lebih dari 50 karakter per baris). Namun, saya kemudian menyadari bahwa saya menggunakan versi FreeBSD dari tail (yang datang dengan OS X secara default). Ketika saya beralih ke ekor GNU, panggilan ekor itu 10 kali lebih cepat dari panggilan sed (dan panggilan sed GNU juga). AaronDigulla benar di sini, jika Anda menggunakan GNU.
Dan Nguyen
179

Anda dapat menggunakan -i untuk memperbarui file tanpa menggunakan operator '>'. Perintah berikut akan menghapus baris pertama dari file dan menyimpannya ke file.

sed -i '1d' filename
ya
sumber
1
Saya mendapatkan kesalahan:unterminated transform source string
Daniel Kobe
10
ini berfungsi setiap saat dan harus benar-benar menjadi jawaban teratas!
xtheking
4
Hanya untuk diingat, Mac membutuhkan akhiran yang harus disediakan saat menggunakan sed dengan suntingan di tempat. Jadi jalankan di atas dengan -i.bak
mjp
3
Hanya sebuah catatan - untuk menghapus beberapa baris gunakansed -i '1,2d' filename
The Godfather
4
Versi ini benar-benar jauh lebih mudah dibaca, dan lebih universal, daripada tail -n +2. Tidak yakin mengapa itu bukan jawaban teratas.
Luke Davis
74

Bagi mereka yang menggunakan SunOS yang bukan GNU, kode berikut akan membantu:

sed '1d' test.dat > tmp.dat 
Nasri Najib
sumber
18
Demografis yang menarik
kapten
17

Tidak, itu seefisien yang akan Anda dapatkan. Anda bisa menulis program C yang bisa melakukan pekerjaan sedikit lebih cepat (lebih sedikit waktu startup dan pemrosesan argumen) tetapi mungkin akan cenderung ke kecepatan yang sama seperti sed file menjadi besar (dan saya menganggap mereka besar jika butuh satu menit ).

Tetapi pertanyaan Anda menderita masalah yang sama seperti banyak orang lain karena itu pra-mengandaikan solusi. Jika Anda memberi tahu kami secara terperinci apa yang Anda coba lakukan daripada bagaimana caranya , kami mungkin dapat menyarankan opsi yang lebih baik.

Misalnya, jika ini adalah file A yang diproses oleh beberapa program B lainnya, salah satu solusinya adalah tidak menghapus baris pertama, tetapi memodifikasi program B untuk memprosesnya secara berbeda.

Katakanlah semua program Anda ditambahkan ke file A ini dan program B saat ini membaca dan memproses baris pertama sebelum menghapusnya.

Anda dapat merekayasa ulang program B sehingga tidak mencoba menghapus baris pertama tetapi mempertahankan offset (mungkin berbasis file) yang persisten ke dalam file A sehingga, saat dijalankan, program dapat mencari proses offset itu, baris di sana, dan perbarui offset.

Kemudian, pada waktu tenang (tengah malam?), Ia bisa melakukan pemrosesan khusus file A untuk menghapus semua baris yang saat ini diproses dan mengatur offset kembali ke 0.

Tentunya akan lebih cepat bagi suatu program untuk membuka dan mencari file daripada membuka dan menulis ulang. Diskusi ini mengasumsikan Anda memiliki kendali atas program B, tentu saja. Saya tidak tahu apakah itu masalahnya tetapi mungkin ada solusi lain yang mungkin jika Anda memberikan informasi lebih lanjut.

paxdiablo
sumber
Saya pikir OP sedang berusaha mencapai apa yang membuat saya menemukan pertanyaan ini. Saya memiliki 10 file CSV dengan 500k baris di masing-masing. Setiap file memiliki baris header yang sama dengan baris pertama. Saya cat: ing file-file ini ke dalam satu file dan kemudian mengimpornya ke dalam DB membiarkan DB membuat nama kolom dari baris pertama. Jelas saya tidak ingin baris itu diulang dalam file 2-10.
db
1
@db Dalam hal ini, awk FNR-1 *.csvmungkin lebih cepat.
jinawee
10

Anda dapat mengedit file di tempat: Cukup gunakan -ibendera perl , seperti ini:

perl -ni -e 'print unless $. == 1' filename.txt

Ini membuat baris pertama menghilang, seperti yang Anda tanyakan. Perl perlu membaca dan menyalin seluruh file, tetapi mengatur agar output disimpan dengan nama file asli.

Alexis
sumber
10

Anda dapat dengan mudah melakukan ini dengan:

cat filename | sed 1d > filename_without_first_line

di baris perintah; atau untuk menghapus baris pertama file secara permanen, gunakan mode sed di tempat dengan -ibendera:

sed -i 1d <filename>
Ingo Baab
sumber
9

Seperti yang dikatakan Pax, Anda mungkin tidak akan mendapatkan yang lebih cepat dari ini. Alasannya adalah bahwa hampir tidak ada sistem file yang mendukung pemotongan sejak awal file sehingga ini akan menjadi operasi O ( n) di mana nukuran file. Apa yang dapat Anda lakukan jauh lebih cepat adalah menimpa baris pertama dengan jumlah byte yang sama (mungkin dengan spasi atau komentar) yang mungkin bekerja untuk Anda tergantung pada apa yang Anda coba lakukan (apa itu omong-omong?).

Robert Gamble
sumber
Re "... hampir tidak ada filesystem yang mendukung pemotongan ..." : itu menarik; harap pertimbangkan untuk menyertakan catatan kurung penamaan sistem file tersebut.
agc
1
@ Agc: tidak relevan sekarang, tetapi pekerjaan pertama saya di tahun 70-an adalah dengan Quadex, sebuah startup kecil (sekarang hilang, dan tidak terkait dengan dua perusahaan yang sekarang menggunakan nama itu). Mereka memiliki sistem file yang memungkinkan penambahan atau penghapusan di awal atau akhir file, yang sebagian besar digunakan untuk mengimplementasikan pengeditan dalam waktu kurang dari 3KB dengan meletakkan file di atas jendela dan di bawah jendela. Itu tidak memiliki nama sendiri, itu hanya bagian dari QMOS, Quadex Multiuser Operating System. ('Multi' biasanya 2-3 pada LSI-11/02 dengan di bawah 64KB RAM dan biasanya beberapa floppy disk tipe RX01 8 "masing-masing 250KB.) :-)
dave_thompson_085
9

The spongeutil menghindari kebutuhan untuk menyulap file temp:

tail -n +2 "$FILE" | sponge "$FILE"
agc
sumber
spongememang jauh lebih bersih dan lebih kuat daripada solusi yang diterima ( tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE")
Jealie
1
Harus diperjelas bahwa 'spons' membutuhkan paket 'moreutils' untuk diinstal.
FedFranzoni
Ini adalah satu-satunya solusi yang berfungsi bagi saya untuk mengubah file sistem (pada gambar buruh pelabuhan Debian). Solusi lain gagal karena kesalahan "Perangkat atau sumber daya sibuk" ketika mencoba untuk menulis file.
FedFranzoni
Tetapi apakah spongebuffer seluruh file dalam memori? Itu tidak akan berfungsi jika itu adalah ratusan GB.
OrangeDog
@OrangeDog, Selama sistem file dapat menyimpannya, spongeakan menyerapnya, karena ia menggunakan file / tmp sebagai langkah perantara, yang kemudian digunakan untuk mengganti yang asli sesudahnya.
agc
8

Jika Anda ingin memodifikasi file di tempat, Anda selalu bisa menggunakan yang asli edbukan yang s penerus treaming sed:

ed "$FILE" <<<$'1d\nwq\n'

The edperintah adalah asli editor teks UNIX, sebelum ada bahkan terminal layar penuh, workstation apalagi grafis. The exEditor, dikenal sebagai apa yang Anda gunakan saat mengetik di usus prompt vi, adalah mantan versi cenderung dari ed, begitu banyak pekerjaan perintah yang sama. Meskipun eddimaksudkan untuk digunakan secara interaktif, itu juga dapat digunakan dalam mode batch dengan mengirimkan serangkaian perintah ke sana, yang merupakan apa yang dilakukan solusi ini.

Urutan <<<$'1d\nwq\n'mengambil keuntungan dari dukungan Bash karena di sini-string ( <<<) dan kutipan POSIX ( $'... ') untuk masukan pakan ke edperintah yang terdiri dari dua baris: 1dyang d eletes baris 1 , dan kemudian wq, yang w ritus file kembali ke disk dan kemudian q UITS sesi editing.

Mark Reed
sumber
ini elegan. +1
Armin
Tetapi Anda harus membaca seluruh file ke dalam memori, yang tidak akan berfungsi jika ratusan GB.
OrangeDog
5

harus menunjukkan baris kecuali baris pertama:

cat textfile.txt | tail -n +2
serup
sumber
4
- Anda harus melakukan "tail -n +2 textfile.txt"
niglesias
5
@niglesiais saya tidak setuju dengan "penggunaan kucing yang tidak berguna", karena memperjelas bahwa solusi ini ok pada konten yang disalurkan dan tidak hanya file.
Titou
5

Bisa menggunakan vim untuk melakukan ini:

vim -u NONE +'1d' +'wq!' /tmp/test.txt

Ini harus lebih cepat, karena vim tidak akan membaca seluruh file saat diproses.

Hongbo Liu
sumber
Mungkin perlu mengutip +wq!jika shell Anda bash. Mungkin bukan karena !tidak pada awal kata, tetapi membiasakan diri mengutip sesuatu mungkin baik di sekitar. (Dan jika Anda menginginkan efisiensi super dengan tidak mengutip yang tidak perlu, Anda tidak perlu mengutipnya 1djuga.)
Mark Reed
vim tidak perlu membaca seluruh file. Sebenarnya jika file lebih besar dari memori, seperti yang ditanyakan dalam Q ini, vim membaca seluruh file dan menulisnya (atau sebagian besar) ke file temp, dan setelah mengedit menulis semuanya kembali (ke file permanen). Saya tidak tahu bagaimana Anda berpikir itu bisa berhasil tanpa ini.
dave_thompson_085
4

Bagaimana dengan menggunakan csplit?

man csplit
csplit -k file 1 '{1}'
Shahbaz
sumber
Sintaks ini akan juga bekerja, tetapi hanya menghasilkan dua file output bukannya tiga: csplit file /^.*$/1. Atau lebih sederhana: csplit file //1. Atau bahkan lebih sederhana: csplit file 2.
Marco Roy
1

Karena sepertinya saya tidak bisa mempercepat penghapusan, saya pikir pendekatan yang baik mungkin untuk memproses file dalam batch seperti ini:

While file1 not empty
  file2 = head -n1000 file1
  process file2
  sed -i -e "1000d" file1
end

Kelemahan dari ini adalah bahwa jika program terbunuh di tengah (atau jika ada sql buruk di sana - menyebabkan bagian "proses" mati atau terkunci), akan ada garis yang dilewati, atau diproses dua kali .

(file1 berisi baris kode sql)

Brent
sumber
Apa isi baris pertama? Bisakah Anda menimpanya dengan komentar sql seperti yang saya sarankan di posting saya?
Robert Gamble
0

Jika yang ingin Anda lakukan adalah memulihkan setelah kegagalan, Anda bisa saja membangun file yang telah Anda lakukan sejauh ini.

if [[ -f $tmpf ]] ; then
    rm -f $tmpf
fi
cat $srcf |
    while read line ; do
        # process line
        echo "$line" >> $tmpf
    done
Tim
sumber
0

Liner satu ini akan melakukan:

echo "$(tail -n +2 "$FILE")" > "$FILE"

Berhasil, karena taildijalankan sebelum echodan kemudian file dibuka, maka tidak perlu untuk file temp.

egor
sumber
-1

Apakah menggunakan tail pada baris N-1 dan mengarahkannya ke file, diikuti dengan menghapus file lama, dan mengganti nama file baru ke nama lama melakukan pekerjaan?

Jika saya melakukan ini secara terprogram, saya akan membaca file, dan mengingat file offset, setelah membaca setiap baris, sehingga saya dapat mencari kembali ke posisi itu untuk membaca file dengan satu baris lebih sedikit di dalamnya.

EvilTeach
sumber
Solusi pertama pada dasarnya identik dengan yang sedang dilakukan Brent. Saya tidak mengerti pendekatan terprogram Anda, hanya baris pertama yang perlu dihapus, Anda hanya perlu membaca dan membuang baris pertama dan menyalin sisanya ke file lain yang lagi sama dengan pendekatan sed dan tail.
Robert Gamble
Solusi kedua memiliki implikasi bahwa file tidak menyusut oleh baris pertama setiap kali. Program hanya memprosesnya, seolah-olah telah menyusut, tetapi mulai dari baris berikutnya setiap kali
EvilTeach
Saya masih tidak mengerti apa solusi kedua Anda.
Robert Gamble