Membaca dan menulis file: perintah tee

10

Sudah diketahui bahwa perintah seperti ini:

cat filename | some_sed_command >filename

menghapus nama file, sebagai pengalihan output, dieksekusi sebelum perintah, menyebabkan nama file terpotong.

Seseorang dapat memecahkan masalah dengan cara berikut:

cat file | some_sed_command | tee file >/dev/null

tapi saya tidak yakin ini akan berhasil dalam hal apapun: apa yang terjadi jika file (dan hasil dari perintah sed) sangat besar? Bagaimana sistem operasi dapat menghindari menimpa beberapa konten yang masih belum dibaca? Saya melihat bahwa ada juga perintah spons yang harus bekerja dalam hal apapun: apakah itu "lebih aman" dari tee?

VeryHardCoder
sumber
Apa tujuan utama anda? (dalam istilah sederhana)
Sergiy Kolodyazhnyy
@Erg hanya mengerti bagaimana hal-hal bekerja ... Jawaban yang ditulis oleh kos mengklarifikasi masalah ini
VeryHardCoder

Jawaban:

10

Seseorang dapat memecahkan masalah dengan cara berikut:

cat file | some_sed_command | tee file >/dev/null

Tidak ada .

Kemungkinan filedrop akan terpotong, tetapi tidak ada jaminan cat file | some_sed_command | tee file >/dev/nulltidak akan terpotong file.

Itu semua tergantung pada perintah mana yang diproses terlebih dahulu, sebagai lawan dari apa yang diharapkan, perintah dalam pipa tidak diproses dari kiri ke kanan . Tidak ada jaminan tentang perintah mana yang akan dipilih pertama, jadi orang mungkin juga menganggapnya sebagai dipilih secara acak dan tidak pernah bergantung pada shell tidak memilih yang menyinggung.

Karena peluang untuk perintah yang salah untuk dipilih pertama di antara tiga perintah lebih rendah daripada peluang untuk perintah yang melanggar untuk dipilih pertama di antara dua perintah, kecil kemungkinannya fileakan dipotong, tetapi itu masih akan terjadi .

script.sh:

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Jadi jangan pernah gunakan sesuatu seperti cat file | some_sed_command | tee file >/dev/null. Gunakan spongeseperti yang disarankan Oli.

Sebagai alternatif, untuk lingkungan yang lebih keras dan / atau file yang relatif kecil seseorang dapat menggunakan string di sini dan substitusi perintah untuk membaca file sebelum perintah apa pun dijalankan:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
kos
sumber
9

Untuk sedkhusus, Anda dapat menggunakan nya -idi tempat argumen. Itu hanya menyimpan kembali ke file yang dibuka, misalnya:

sed -i 's/ /-/g' filename

Jika Anda ingin melakukan sesuatu yang lebih keren, dengan anggapan Anda melakukan lebih dari sed, ya, Anda dapat menyangga semuanya dengan sponge(dari moreutilspaket) yang akan "menyerap" semua stdin sebelum menulis ke file. Ini seperti teetetapi dengan fungsionalitas yang lebih sedikit. Namun untuk penggunaan dasar, ini merupakan pengganti drop-in:

cat file | some_sed_command | sponge file >/dev/null

Apakah itu lebih aman? Pastinya. Ini mungkin memiliki batas melalui jadi jika Anda melakukan sesuatu yang kolosal (dan tidak dapat di-edit dengan sed), Anda mungkin ingin melakukan pengeditan untuk file kedua dan kemudian mvfile itu kembali ke nama file asli. Itu harus atomik (jadi apa pun yang tergantung pada file-file ini tidak akan pecah jika mereka membutuhkan akses konstan).

Oli
sumber
0

Anda dapat menggunakan Vim dalam mode Ex:

ex -sc '%!some_sed_command' -cx filename
  1. % pilih semua baris

  2. ! Jalankan perintah

  3. x Simpan dan keluar

Steven Penny
sumber
0

Oh, tetapi spongebukan satu-satunya pilihan; Anda tidak harus mendapatkan moreutilsuntuk mendapatkan ini berfungsi dengan benar. Mekanisme apa pun akan berfungsi selama memenuhi dua persyaratan berikut:

  1. Ini menerima nama file output sebagai parameter.
  2. Ini hanya membuat file output setelah semua input diproses.

Anda lihat, masalah yang diketahui oleh OP adalah bahwa shell akan membuat semua file yang diperlukan untuk pipa untuk bekerja sebelum bahkan mulai menjalankan perintah dalam pipeline, jadi itu adalah shell yang benar-benar memotong file output (yang sayangnya juga merupakan file input) sebelum salah satu perintah bahkan memiliki kesempatan untuk memulai eksekusi.

Itu tee perintah tidak bekerja, meskipun itu memenuhi persyaratan pertama, karena tidak memenuhi persyaratan kedua: akan selalu membuat file output segera setelah mulai, jadi pada dasarnya seburuk menciptakan pipa langsung ke file output. (Ini sebenarnya lebih buruk, karena penggunaannya memperkenalkan penundaan acak non-deterministik sebelum file output terpotong, jadi Anda mungkin berpikir itu berfungsi, padahal sebenarnya tidak.)

Jadi, yang kita butuhkan untuk menyelesaikan masalah ini adalah beberapa perintah yang akan buffer semua inputnya sebelum menghasilkan output, dan yang mampu menerima nama file output sebagai parameter, sehingga kita tidak perlu menyalurkan outputnya ke dalam file output. Salah satu perintah itu adalah shuf. Jadi, berikut ini akan mencapai hal yang sama dengan yang spongedilakukan:

    shuf --output=file --random-source=/dev/zero 

Bagian --random-source=/dev/zerotrik shufmelakukan hal itu tanpa melakukan pengocokan sama sekali, sehingga akan buffer input Anda tanpa mengubahnya.

Mike Nakis
sumber