Pada dasarnya saya ingin mengambil teks input dari file, menghapus baris dari file itu, dan mengirim output kembali ke file yang sama. Sesuatu di sepanjang garis ini jika itu membuatnya lebih jelas.
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name
Namun, ketika saya melakukan ini, saya berakhir dengan file kosong. Ada pemikiran?
Jawaban:
Anda tidak dapat melakukannya karena bash memproses pengalihan terlebih dahulu, lalu menjalankan perintah. Jadi pada saat grep melihat nama_file, itu sudah kosong. Anda dapat menggunakan file sementara.
#!/bin/sh tmpfile=$(mktemp) grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile} cat ${tmpfile} > file_name rm -f ${tmpfile}
seperti itu, pertimbangkan
mktemp
untuk menggunakan untuk membuat tmpfile tetapi perhatikan bahwa ini bukan POSIX.sumber
>
pengalihan akan membuka file dan memotongnya sebelum shell diluncurkangrep
.sponge
perintah harus diterima.Gunakan spons untuk tugas semacam ini. Bagian dari moreutils.
Coba perintah ini:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name
sumber
brew install moreutils
.sudo apt-get install moreutils
pada sistem berbasis Debian.Gunakan sed sebagai gantinya:
sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name
sumber
-i
adalah ekstensi GNU saja, hanya mencatat.-i ''
bahwa ekstensi tidak sepenuhnya wajib, tetapi-i
opsi tersebut memerlukan beberapa argumen.coba yang sederhana ini
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
File Anda tidak akan kosong kali ini :) dan keluaran Anda juga akan dicetak ke terminal Anda.
sumber
/dev/null
atau tempat serupa.Anda tidak dapat menggunakan operator pengalihan (
>
atau>>
) ke file yang sama, karena memiliki prioritas yang lebih tinggi dan akan membuat / memotong file bahkan sebelum perintah dipanggil. Untuk menghindari itu, Anda harus menggunakan alat yang tepat sepertitee
,sponge
,sed -i
atau alat lain yang dapat menulis hasil ke file (misalnyasort file -o file
).Pada dasarnya mengarahkan input ke file asli yang sama tidak masuk akal dan Anda harus menggunakan editor di tempat yang sesuai untuk itu, misalnya Ex editor (bagian dari Vim):
ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name
dimana:
'+cmd'
/-c
- jalankan perintah Ex / Vimg/pattern/d
- hapus garis yang cocok dengan pola menggunakan global (help :g
)-s
- mode diam (man ex
)-c wq
- jalankan:write
dan:quit
perintahAnda dapat menggunakan
sed
untuk mencapai yang sama (seperti yang sudah ditunjukkan dalam jawaban lainnya), namun di tempat (-i
) adalah non-standar ekstensi FreeBSD (dapat bekerja secara berbeda antara Unix / Linux) dan pada dasarnya itu adalah s tream ed itor, bukan file editor . Lihat: Apakah mode Ex memiliki kegunaan praktis?sumber
Alternatif satu liner - setel konten file sebagai variabel:
VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name
sumber
Karena pertanyaan ini adalah hasil teratas di mesin pencari, berikut adalah satu baris berdasarkan https://serverfault.com/a/547331 yang menggunakan subkulit daripada
sponge
(yang seringkali bukan bagian dari instalasi vanilla seperti OS X) :echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name
Kasus umumnya adalah:
echo "$(cat file_name)" > file_name
Sunting, solusi di atas memiliki beberapa peringatan:
printf '%s' <string>
harus digunakan sebagai penggantiecho <string>
agar file yang berisi-n
tidak menyebabkan perilaku yang tidak diinginkan.x
ke output dan menghapusnya di luar melalui perluasan parameter dari variabel sementara seperti${v%x}
.$v
menginjak nilai variabel yang ada$v
di lingkungan shell saat ini, jadi kita harus menyarangkan seluruh ekspresi dalam tanda kurung untuk mempertahankan nilai sebelumnya.null
dari output. Saya memverifikasi ini dengan memanggildd if=/dev/zero bs=1 count=1 >> file_name
dan melihatnya dalam hex dengancat file_name | xxd -p
. Tapiecho $(cat file_name) | xxd -p
dilucuti. Jadi, jawaban ini tidak boleh digunakan pada file biner atau apa pun yang menggunakan karakter yang tidak dapat dicetak, seperti yang ditunjukkan Lynch .Solusi umum (albiet sedikit lebih lambat, memori lebih intensif dan masih menghilangkan karakter yang tidak dapat dicetak) adalah:
(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)
Uji dari https://askubuntu.com/a/752451 :
printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do (v=$(cat file_uniquely_named.txt; printf x); printf '%s' ${v%x} > file_uniquely_named.txt); done; cat file_uniquely_named.txt; rm file_uniquely_named.txt
Harus mencetak:
Sedangkan memanggil
cat file_uniquely_named.txt > file_uniquely_named.txt
di shell saat ini:printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do cat file_uniquely_named.txt > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt
Mencetak string kosong.
Saya belum menguji ini pada file besar (mungkin lebih dari 2 atau 4 GB).
Saya telah meminjam jawaban ini dari Hart Simha dan kos .
sumber
cat
dan meletakkannya sebagai argumen pertamaecho
. Tentu saja variabel yang tidak dapat dicetak tidak akan menghasilkan keluaran dengan benar dan merusak data. Jangan mencoba untuk mengarahkan kembali file ke dirinya sendiri, itu tidak baik.Ada juga
ed
(sebagai alternatifsed -i
):# cf. http://wiki.bash-hackers.org/howto/edit-ed printf '%s\n' H 'g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' wq | ed -s file_name
sumber
Anda dapat melakukannya dengan menggunakan proses-substitusi .
Ini sedikit peretasan karena bash membuka semua pipa secara tidak sinkron dan kami harus mengatasinya menggunakan
sleep
begitu YMMV.Dalam contoh Anda:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
>(sleep 1 && cat > file_name)
membuat file sementara yang menerima keluaran dari grepsleep 1
penundaan sedetik untuk memberi grep waktu untuk mengurai file inputcat > file_name
menulis hasilnyasumber
Anda dapat menggunakan slurp dengan POSIX Awk:
!/seg[0-9]\{1,\}\.[0-9]\{1\}/ { q = q ? q RS $0 : $0 } END { print q > ARGV[1] }
Contoh
sumber
Ini sangat mungkin, Anda hanya perlu memastikan bahwa pada saat Anda menulis hasilnya, Anda menulisnya ke file yang berbeda. Ini dapat dilakukan dengan menghapus file setelah membuka deskriptor file, tetapi sebelum menulis padanya:
exec 3<file ; rm file; COMMAND <&3 >file ; exec 3>&-
Atau baris demi baris, untuk lebih memahaminya:
exec 3<file # open a file descriptor reading 'file' rm file # remove file (but fd3 will still point to the removed file) COMMAND <&3 >file # run command, with the removed file as input exec 3>&- # close the file descriptor
Ini masih berisiko untuk dilakukan, karena jika COMMAND gagal berjalan dengan benar, Anda akan kehilangan konten file. Itu dapat dikurangi dengan memulihkan file jika COMMAND mengembalikan kode keluar bukan nol:
exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-
Kita juga bisa mendefinisikan fungsi shell agar lebih mudah digunakan:
# Usage: replace FILE COMMAND replace() { exec 3<$1 ; rm $1; ${@:2} <&3 >$1 || cat <&3 >$1 ; exec 3>&- }
Contoh:
$ echo aaa > test $ replace test tr a b $ cat test bbb
Juga, perhatikan bahwa ini akan menyimpan salinan lengkap dari file asli (hingga deskriptor file ketiga ditutup). Jika Anda menggunakan Linux, dan file yang Anda proses terlalu besar untuk memuat dua kali pada disk, Anda dapat memeriksa skrip ini yang akan menyalurkan file ke perintah yang ditentukan blok-demi-blok sambil membatalkan alokasi yang sudah diproses blok. Seperti biasa, baca peringatan di halaman penggunaan.
sumber
Coba ini
echo -e "AAA\nBBB\nCCC" > testfile cat testfile AAA BBB CCC echo "$(grep -v 'AAA' testfile)" > testfile cat testfile BBB CCC
sumber
Yang berikut ini akan mencapai hal yang sama dengan yang
sponge
dilakukannya, tanpa memerlukanmoreutils
:Bagian
--random-source=/dev/zero
trikshuf
melakukan sesuatu tanpa melakukan pengacakan sama sekali, jadi itu akan menyangga masukan Anda tanpa mengubahnya.Namun, memang benar bahwa menggunakan file sementara adalah yang terbaik, karena alasan kinerja. Jadi, berikut adalah fungsi yang telah saya tulis yang akan melakukannya untuk Anda secara umum:
# Pipes a file into a command, and pipes the output of that command # back into the same file, ensuring that the file is not truncated. # Parameters: # $1: the file. # $2: the command. (With $3... being its arguments.) # See https://stackoverflow.com/a/55655338/773113 function siphon { local tmp=$(mktemp) local file="$1" shift $* < "$file" > "$tmp" mv "$tmp" "$file" }
sumber
Saya biasanya menggunakan program tee untuk melakukan ini:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
Ini membuat dan menghapus tempfile dengan sendirinya.
sumber
tee
tidak dijamin berhasil. Lihat askubuntu.com/a/752451/335781 .