Linux: Bagaimana cara menggunakan file sebagai input dan output pada saat yang bersamaan?

55

Saya baru saja menjalankan yang berikut ini di bash:

uniq .bash_history > .bash_history

dan file riwayat saya akhirnya benar-benar kosong.

Saya kira saya perlu cara untuk membaca seluruh file sebelum menulis untuk itu. Bagaimana itu dilakukan?

PS: Jelas saya berpikir untuk menggunakan file sementara, tapi saya mencari solusi yang lebih elegan.

MilliaLover
sumber
Itu karena file dibuka dari kanan ke kiri. Lihat juga stackoverflow.com/questions/146435/…
WheresAlice
Anda harus menulis output ke file baru di direktori yang sama dan beri nama di atas file lama. Pendekatan lain apa pun akan berisiko kehilangan data Anda jika terputus di tengah jalan. Beberapa alat mungkin menyembunyikan langkah ini dari Anda.
kasperd
Atau, bashtidak akan membuat dupes berurutan dalam riwayatnya jika Anda menetapkan HISTCONTROL untuk memasukkan diabaikan; lihat halaman manual.
dave_thompson_085

Jawaban:

49

Saya sarankan menggunakan spongedari moreutils . Dari halaman manual:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

Untuk menerapkan ini pada masalah Anda, coba:

uniq .bash_history | sponge .bash_history
jldugger
sumber
6
Ini seperti kucing, tetapi dengan kemampuan mengisap: D
MilliaLover
77

Saya hanya ingin menyumbangkan jawaban lain yang sederhana, dan tidak menggunakan spons (karena sering tidak termasuk dalam lingkungan yang ringan).

echo "$(uniq .bash_history)" > .bash_history

harus memiliki hasil yang diinginkan. Subshell dijalankan sebelum .bash_history dibuka untuk ditulis. Seperti dijelaskan dalam jawaban Phil P, pada saat .bash_history dibaca dalam perintah asli, sudah terpotong oleh operator '>'.

Hart Simha
sumber
15
Saya biasanya bukan penggemar jawaban yang mengeruk pertanyaan kuno yang sudah memiliki jawaban yang valid dan diterima - tetapi ini elegan, ditulis dengan baik, dan membuat argumen yang kuat untuk keperluannya (lingkungan yang ringan); bagi saya, itu benar-benar menambahkan sesuatu ke set jawaban yang ada. Selamat datang di SF, Hart (Anda sudah di sini sebulan, tapi saya pikir ini adalah posting substantif pertama Anda). Saya berharap dapat membaca lebih banyak jawaban dari Anda seperti ini!
MadHatter
4
Ini solusi terbaik. Saya harus menggunakan subshell $()bukan backticks karena beberapa masalah lolos.
CMCDragonkai
3
Saya bertanya-tanya apakah solusi ini menskala ke file besar, katakanlah ... 20 atau 50 GB.
Amit Naidu
1
Ini benar-benar harus menjadi jawaban terima.
maxywb
1
Saya menggunakan jawaban ini untuk dilakukan echo "$(fmt -p '# ' -w 50 readme.txt)" > readme.txthari ini. Sedang mencari-cari solusi yang elegan untuk waktu yang lama. Banyak terima kasih, @Hart Simha!
shredalert
12

Masalahnya adalah shell Anda mengatur pipeline perintah sebelum menjalankan perintah. Ini bukan masalah "input dan output", melainkan bahwa konten file sudah hilang bahkan sebelum uniq berjalan. Bunyinya seperti:

  1. Shell membuka >file output untuk menulis, memotongnya
  2. Shell mengatur agar file-descriptor 1 (untuk stdout) digunakan untuk output itu
  3. Shell mengeksekusi uniq, mungkin sesuatu seperti execlp ("uniq", "uniq", ".bash_history", NULL)
  4. uniq berjalan, membuka .bash_history dan tidak menemukan apa pun di sana

Ada berbagai solusi, termasuk pengeditan di tempat dan penggunaan file sementara yang disebutkan orang lain, tetapi kuncinya adalah untuk memahami masalahnya, apa yang sebenarnya salah dan mengapa.

Phil P
sumber
9

Trik lain untuk melakukan ini, tanpa menggunakan sponge, adalah perintah berikut:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

Ini adalah salah satu cheat yang dijelaskan dalam artikel yang sangat baik "Di tempat" mengedit file di backreference.org.

Ini pada dasarnya membuka file untuk dibaca, kemudian "menghapusnya". Namun, itu tidak benar-benar dihapus: Ada deskriptor file terbuka yang menunjuk ke sana, dan selama itu tetap terbuka, file tersebut masih ada. Kemudian ia membuat file baru dengan nama yang sama dan menulis baris unik untuk itu.

Kerugian dari solusi ini: Jika uniqgagal karena alasan tertentu, riwayat Anda akan hilang.

scy
sumber
6

gunakan spons dari moreutils

uniq .bash_history | sponge .bash_history
Justin
sumber
3

sedScript ini menghapus duplikat yang berdekatan. Dengan -iopsi tersebut, ia melakukan modifikasi di tempat. Itu dari sed infofile:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history
Dennis Williamson
sumber
sed masih menggunakan file temp, menambahkan jawaban dengan straceilustrasi (bukan berarti itu penting) :-)
Kyle Brandt
3
@Kyle: Benar, tapi "tidak terlihat, tidak terpikirkan". Secara pribadi, saya akan menggunakan file sementara eksplisit karena sesuatu seperti process input > tmp && mv tmp inputjauh lebih sederhana dan lebih mudah dibaca daripada menggunakan sedtipuan hanya untuk menghindari file temp dan itu tidak akan menimpa dokumen asli saya jika gagal (saya tidak tahu jika sed -igagal dengan anggun - saya akan pikir itu akan). Selain itu, ada banyak hal yang dapat Anda lakukan dengan metode output-ke-temp-file yang tidak dapat dilakukan di tempat tanpa sesuatu yang bahkan lebih terlibat daripada sedskrip ini . Saya tahu Anda tahu semua ini, tetapi mungkin bermanfaat bagi beberapa penonton.
Dennis Williamson
3

Sebagai berita menarik, sed juga menggunakan file temp (ini hanya berlaku untuk Anda):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

Deskripsi:
Tempfile ./sedPmPv9zmenjadi fd 4, dan foofile menjadi fd 3. Operasi baca adalah pada fd 3, dan penulisan pada fd 4 (file temp). File foo kemudian ditimpa dengan file temp di panggilan rename.

Kyle Brandt
sumber
1

Solusi lain:

uniq file 1<> líneas.txt
Antonio Lebrón
sumber
0

File sementara cukup banyak, kecuali perintah yang dimaksud terjadi untuk mendukung pengeditan di tempat ( uniqtidak - beberapa orang sedmelakukannya ( sed -i)).

Douglas Leeder
sumber
0

Anda dapat menggunakan Vim dalam mode Ex:

ex -sc '%!uniq' -cx .bash_history
  1. % pilih semua garis

  2. ! jalankan perintah

  3. x Simpan dan tutup

Steven Penny
sumber
-1

Anda dapat menggunakan tee juga, menggunakan output uniq sebagai input:

uniq .bash_history | tee .bash_history
Saul Vargas
sumber
Tidak, Anda tidak dapat melakukan ini askubuntu.com/a/752451
Steven Penny