Bagaimana cara menjalankan perintah yang mengedit file (argumen) "di tempat" menggunakan bash?

110

Saya memiliki file temp.txt, yang ingin saya sortir dengan sort perintah di bash.

Saya ingin hasil yang diurutkan menggantikan file asli.

Ini tidak berfungsi misalnya (saya mendapatkan file kosong):

sortx temp.txt > temp.txt

Bisakah ini dilakukan dalam satu baris tanpa harus menyalin ke file sementara?


EDIT: -oOpsinya sangat keren sort. Saya menggunakan sortpertanyaan saya sebagai contoh. Saya mengalami masalah yang sama dengan perintah lain:

uniq temp.txt > temp.txt.

Apakah ada solusi umum yang lebih baik?

jm.
sumber
Juga lihat serverfault.com/a/547331/313521
Wildcard

Jawaban:

171
sort temp.txt -o temp.txt
daniels
sumber
3
Ini jawabannya. Saya sebenarnya bertanya-tanya apakah ada solusi umum untuk masalah ini. Misalnya jika saya ingin mencari semua baris UNIQ dalam file "di tempat", saya tidak bisa melakukan -o
jm.
Ini tidak umum, tetapi Anda dapat menggunakan -u dengan jenis GNU untuk menemukan baris unik
James
Adakah yang memecahkan masalah untuk mengizinkan misalnya sort --inplace *.txt? Itu akan sangat keren
lihat
@sehe Coba ini:find . -name \*.txt -exec sort {} -o {} \;
Keith Gaughan
29

A sortperlu melihat semua masukan sebelum dapat mulai menghasilkan. Karena alasan ini, sortprogram dapat dengan mudah menawarkan opsi untuk mengubah file di tempat:

sort temp.txt -o temp.txt

Secara khusus, dokumentasi GNUsort mengatakan:

Biasanya, sort membaca semua masukan sebelum membuka file keluaran, sehingga Anda dapat mengurutkan file dengan aman menggunakan perintah seperti sort -o F Fdan cat F | sort -o F. Namun, sortdengan --merge( -m) dapat membuka file output sebelum membaca semua input, jadi perintah seperti cat F | sort -m -o F - Gini tidak aman karena sort mungkin mulai menulis Fsebelum catselesai membacanya.

Sedangkan dokumentasi BSD sortmenyebutkan:

Jika file output adalah salah satu file input, sortir salinannya ke file sementara sebelum menyortir dan menulis output ke file output.

Perintah seperti uniq dapat mulai menulis keluaran sebelum selesai membaca masukan. Perintah ini biasanya tidak mendukung pengeditan di tempat (dan akan lebih sulit bagi mereka untuk mendukung fitur ini).

Anda biasanya mengatasinya dengan file sementara, atau jika Anda benar-benar ingin menghindari file perantara, Anda dapat menggunakan buffer untuk menyimpan hasil lengkap sebelum menulisnya. Misalnya dengan perl:

uniq temp.txt | perl -e 'undef $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'

Di sini, bagian perl membaca keluaran lengkap dari uniqdalam variabel $_dan kemudian menimpa file asli dengan data ini. Anda dapat melakukan hal yang sama dalam bahasa skrip pilihan Anda, bahkan mungkin di Bash. Tetapi perhatikan bahwa itu akan membutuhkan cukup memori untuk menyimpan seluruh file, ini tidak disarankan saat bekerja dengan file besar.

Bruno De Fraine
sumber
19

Berikut pendekatan yang lebih umum, bekerja dengan uniq, sort dan yang lainnya.

{ rm file && uniq > file; } < file
pekerjaan
sumber
14
Pendekatan lain generik, dengan spongedari MoreUtils: cat file |frobnicate |sponge file.
Tobu
3
@Tobu: mengapa tidak mengirimkan itu sebagai jawaban terpisah?
Flimm
1
Mungkin bagus untuk dicatat bahwa ini tidak selalu mempertahankan izin file. Umask Anda menentukan apa izin baru nantinya.
wor
1
Yang rumit. Bisakah Anda menjelaskan bagaimana cara kerjanya?
patryk.beza
2
@ patryk.beza: Secara berurutan: FD input dibuka dari file asli; entri direktori asli dihapus; pengalihan diproses, membuat file kosong baru dengan nama yang sama dengan yang lama digunakan; lalu perintah tersebut dijalankan.
Charles Duffy
10

Komentar Tobu tentang jaminan spons menjadi jawaban tersendiri.

Mengutip dari beranda moreutils :

Mungkin alat tujuan paling umum di moreutils sejauh ini adalah spons (1), yang memungkinkan Anda melakukan hal-hal seperti ini:

% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd

Namun, Steve Jessopsponge menderita masalah yang sama dengan komentar di sini. Jika salah satu perintah dalam pipeline sebelumnya spongegagal, maka file asli akan diganti.

$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found

Uh-oh, my-important-filehilang.

Sean
sumber
1
Sponge tahu bahwa itu akan digunakan untuk mengganti file masukan dan awalnya membuat file temporer untuk menghindari kondisi balapan. Agar ini berfungsi, spons harus menjadi elemen terakhir dalam pipeline dan harus diizinkan untuk membuat file keluaran itu sendiri (sebagai lawan pengalihan keluaran tingkat shell, misalnya). BTW: Sepertinya perbaikan kode sumber yang mudah untuk kasus 'gagal' adalah dengan tidak mengganti nama file temp dalam kasus pipefail (tidak tahu mengapa sponge tidak memiliki opsi itu).
Brent Bradburn
Saya pikir jika Anda menambahkan set -o pipefaildi awal skrip Anda, kesalahan pada mistyped_command my-important-fileakan membuat skrip segera keluar, sebelum dijalankan sponge, sehingga menjaga file penting.
Elouan Keryell-Even
6

Ini dia, satu baris:

sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt

Secara teknis tidak ada penyalinan ke file sementara, dan perintah 'mv' harus instan.

davr
sumber
6
Hm. Saya masih menyebut temp.txt.sort sebagai file sementara.
JesperE
5
Kode ini berisiko, karena jika pengurutan gagal karena alasan apa pun tanpa menyelesaikan tugasnya, yang asli akan ditimpa.
Steve Jessop
1
Kurangnya ruang disk menjadi penyebab yang masuk akal, atau sinyal (pengguna menekan CTRL-C).
Steve Jessop
5
jika Anda ingin menggunakan sesuatu seperti ini gunakan && (logis dan) daripada; karena menggunakan itu akan memastikan bahwa jika sebuah perintah gagal selanjutnya tidak akan dijalankan. misalnya: cp backup.tar /root/backup.tar && rm backup.tar jika Anda tidak memiliki hak untuk menyalin, Anda akan aman karena file tidak akan dihapus
daniels
1
mengubah jawaban saya untuk mempertimbangkan saran Anda, terima kasih
davr
4

Saya suka sort file -o filejawabannya tetapi tidak ingin mengetik nama file yang sama dua kali.

Menggunakan ekspansi riwayat BASH :

$ sort file -o !#^

mengambil argumen pertama baris saat ini saat Anda menekan enter.

Urutan unik di tempat:

$ sort -u -o file !#$

mengambil argumen terakhir di baris saat ini.

johnnyB
sumber
3

Banyak yang menyebutkan opsi -o . Ini adalah bagian halaman manual.

Dari halaman manual:

   -o output-file
          Write output to output-file instead of to the  standard  output.
          If  output-file  is  one of the input files, sort copies it to a
          temporary file before sorting and writing the output to  output-
          file.
epatel
sumber
3

Ini akan sangat membatasi memori, tetapi Anda dapat menggunakan awk untuk menyimpan data perantara dalam memori, dan kemudian menuliskannya kembali.

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt
JayG
sumber
Saya pikir mungkin saja >memotong file sebelum perintah ( uniqdalam hal ini) membacanya.
Martin
3

Alternatif spongedengan yang lebih umum sed:

sed -ni r<(command file) file

Ia bekerja untuk perintah apapun ( sort, uniq, tac, ...) dan penggunaan yang sangat terkenal sed's -ipilihan (mengedit file di tempat).

Peringatan: Coba command filedulu karena mengedit file di tempat pada dasarnya tidak aman.


Penjelasan

Pertama, Anda mengatakan sedtidak untuk mencetak (asli) baris ( -noption ), dan dengan bantuan dari sed's rperintah dan bash' s Proses Pergantian , konten yang dihasilkan oleh <(command file)akan menjadi output disimpan di tempat .


Membuat segalanya lebih mudah

Anda dapat menggabungkan solusi ini menjadi sebuah fungsi:

ip_cmd() { # in place command
    CMD=${1:?You must specify a command}
    FILE=${2:?You must specify a file}
    sed -ni r<("$CMD" "$FILE") "$FILE"
}

Contoh

$ cat file
d
b
c
b
a

$ ip_cmd sort file
$ cat file
a
b
b
c
d

$ ip_cmd uniq file
$ cat file
a
b
c
d

$ ip_cmd tac file
$ cat file
d
c
b
a

$ ip_cmd
bash: 1: You must specify a command
$ ip_cmd uniq
bash: 2: You must specify a file
whoan
sumber
1

Gunakan argumen --output=atau-o

Baru saja mencoba FreeBSD:

sort temp.txt -otemp.txt
sammyo
sumber
Meskipun benar, itu hanyalah duplikat dari jawaban ini
whoan
1

Untuk menambah uniqkemampuan, apa kerugiannya:

sort inputfile | uniq | sort -o inputfile
jasper
sumber
1

Baca di editor non-interaktif ex,.

ramping
sumber
heh - itu ide yang sangat jahat. Saya suka itu.
David Mackintosh
0

Jika Anda bersikeras menggunakan sortprogram ini, Anda harus menggunakan file perantara - saya rasa tidak sortmemiliki opsi untuk menyortir dalam memori. Trik lain dengan stdin / stdout akan gagal kecuali Anda dapat menjamin bahwa ukuran buffer untuk stdin sort cukup besar untuk memuat seluruh file.

Sunting: malu padaku. sort temp.txt -o temp.txtbekerja dengan sangat baik.

JesperE
sumber
Saya membaca Q juga sebagai "di tempat" tetapi pembacaan kedua membuat saya percaya dia tidak benar-benar memintanya
epatel
0

Solusi lain:

uniq file 1<> file
Antonio Lebrón
sumber
Perlu dicatat bahwa <>trik hanya berfungsi dalam kasus ini karena uniqkhusus karena hanya menyalin jalur input ke jalur output, menjatuhkan beberapa di jalan. Jika perintah lain (misalnya sed) digunakan yang akan mengubah masukan (misalnya akan mengubah setiap amenjadi aa), maka perintah tersebut dapat menimpa filedengan cara yang tidak masuk akal dan bahkan berulang tanpa batas, asalkan masukan cukup besar (lebih dari satu buffer baca tunggal).
David