dan edit file di tempat

300

Saya mencoba mencari tahu apakah mungkin untuk mengedit file dalam perintah sed tunggal tanpa secara manual mengalirkan konten yang diedit ke file baru dan kemudian mengubah nama file baru ke nama file asli. Saya mencoba -iopsi tetapi sistem Solaris saya mengatakan itu -iadalah opsi ilegal. Apakah ada cara yang berbeda?

amfibi
sumber
7
-iadalah pilihan di gnu sed, tetapi tidak dalam sed standar. Namun, itu mengalirkan konten ke file baru dan kemudian mengganti nama file sehingga bukan yang Anda inginkan.
William Pursell
2
sebenarnya, itu yang saya inginkan, saya hanya ingin tidak terkena harus melakukan tugas biasa mengganti nama file baru ke nama asli
amfibi
3
Maka Anda perlu menyatakan kembali pertanyaan itu.
William Pursell
3
@amphibient: Apakah Anda keberatan mengawali judul pertanyaan Anda dengan kata 'Solaris'? Nilai pertanyaan Anda hilang. Silakan lihat komentar di bawah jawaban saya. Terima kasih.
Steve
1
@Steve: Saya menghapus awalan Solaris dari judul lagi karena ini sama sekali tidak eksklusif untuk Solaris.
tripleee

Jawaban:

477

The -ipilihan stream konten diedit ke dalam sebuah file baru dan kemudian mengganti nama belakang layar, anyway.

Contoh:

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

dan

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

pada MacOS .

choroba
sumber
3
setidaknya itu untukku jadi aku tidak perlu
amfibi
2
Anda dapat mencoba mengkompilasi GNU sed pada sistem Anda, kemudian.
choroba
3
contoh:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin
2
Ini lebih baik daripada solusi perl. Entah bagaimana itu tidak membuat file .swap .... terima kasih!
matematika
3
@ maths: Ya, tapi mungkin di tempat lain atau untuk waktu yang lebih singkat?
choroba
72

Pada sistem di mana sedtidak memiliki kemampuan untuk mengedit file di tempat, saya pikir solusi yang lebih baik adalah menggunakan perl:

perl -pi -e 's/foo/bar/g' file.txt

Meskipun ini memang membuat file sementara, ini menggantikan yang asli karena sufiks / ekstensi kosong telah disediakan.

Steve
sumber
14
Tidak hanya membuat file sementara, itu juga memecah tautan keras (misalnya, alih-alih mengubah isi file, itu mengganti file dengan file baru dengan nama yang sama.) Kadang-kadang ini adalah perilaku yang diinginkan, kadang-kadang dapat diterima, tetapi ketika ada beberapa tautan ke suatu file itu hampir selalu merupakan perilaku yang salah.
William Pursell
3
@DwightSpencer: Saya percaya bahwa semua sistem tanpa Perl rusak. Perintah tunggal mengalahkan rantai perintah ketika seseorang menginginkan izin yang ditinggikan dan harus digunakan sudo. Dalam pikiran saya, pertanyaannya adalah tentang bagaimana menghindari membuat dan mengganti nama file sementara secara manual tanpa harus menginstal GNU atau BSD terbaru sed. Cukup gunakan Perl.
Steve
4
@PetrPeller: Benarkah? Jika Anda membaca pertanyaan dengan seksama, Anda akan memahami bahwa OP berusaha menghindari sesuatu seperti sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt,. Hanya karena pengeditan di tempat melakukan penggantian nama menggunakan file sementara, tidak berarti ia harus melakukan pengubahan nama secara manual ketika opsi tidak tersedia. Ada alat lain di luar sana yang dapat melakukan apa yang dia inginkan, dan Perl adalah pilihan yang jelas. Bagaimana ini bukan jawaban untuk pertanyaan awal?
Steve
2
@ a_arias: Anda benar; dia melakukan. Tetapi dia tidak dapat mencapai apa yang dia inginkan menggunakan Solaris sed. Pertanyaan itu sebenarnya sangat bagus, tetapi nilainya telah berkurang oleh orang-orang yang tidak dapat membaca halaman manual atau tidak dapat diganggu untuk benar - benar membaca pertanyaan di SO.
Steve
4
@ EdwardGarson: IIRC, Solaris dikirimkan dengan Perl tetapi tidak dengan Python atau Ruby. Dan seperti yang saya katakan sebelumnya, OP tidak dapat mencapai hasil yang diinginkan menggunakan Solaris sed.
Steve
56

Perhatikan bahwa pada OS X Anda mungkin mendapatkan kesalahan aneh seperti "kode perintah tidak valid" atau kesalahan aneh lainnya saat menjalankan perintah ini. Untuk memperbaiki masalah ini coba

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

Ini karena pada versi OSX sed, -iopsi mengharapkan extensionargumen sehingga perintah Anda sebenarnya diuraikan sebagai extensionargumen dan jalur file ditafsirkan sebagai kode perintah. Sumber: https://stackoverflow.com/a/19457213

Diadyne
sumber
1
Ini adalah keanehan lain dari OS X. Untungnya, brew install gnu-sedbersama dengan PATHakan memungkinkan Anda untuk menggunakan versi sed GNU. Terima kasih telah menyebutkan; penggaruk kepala yang pasti.
Doug
23

Berikut ini berfungsi dengan baik di mac saya

sed -i.bak 's/foo/bar/g' sample

Kami mengganti foo dengan bar di file sampel. Cadangan file asli akan disimpan di sample.bak

Untuk mengedit inline tanpa cadangan, gunakan perintah berikut

sed -i'' 's/foo/bar/g' sample
minhas23
sumber
Adakah cara bagaimana mencegah menyimpan file cadangan?
Petr Peller
@PetrPeller, Anda dapat menggunakan perintah yang disebutkan untuk sampel yang sama ... sed -i / foo / bar / g '
minhas23
18

Satu hal yang perlu diperhatikan, sedtidak dapat menulis file sendiri sebagai satu-satunya tujuan sed adalah untuk bertindak sebagai editor pada "aliran" (yaitu pipa stdin, stdout, stderr, dan >&nbuffer lain , soket dan sejenisnya). Dengan mengingat hal ini, Anda dapat menggunakan perintah lain teeuntuk menulis output kembali ke file. Pilihan lain adalah membuat tambalan dari menyalurkan konten ke dalamnya diff.

Metode tee

sed '/regex/' <file> | tee <file>

Metode tambalan

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

MEMPERBARUI:

Juga, perhatikan bahwa tambalan akan membuat file berubah dari baris 1 dari output diff:

Tambalan tidak perlu tahu file mana yang akan diakses karena ini ditemukan di baris pertama dari keluaran dari diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar
Dwight Spencer
sumber
2
/dev/stdin 2014-03-15, jawab 7 Jan - apakah Anda seorang penjelajah waktu?
Adrian Frühwirth
Di Windows, menggunakan msysgit, /dev/stdintidak ada, jadi Anda harus mengganti /dev/stdindengan '-', tanda hubung tunggal tanpa tanda kutip, jadi perintah berikut ini akan berfungsi: $ sed 's/oo/u/' fubar | diff -p fubar -dan$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden
@ AdrianFrühwirth Saya punya kotak polisi biru di sekitar sini dan untuk beberapa alasan mereka memanggil saya Dokter di tempat kerja. Tapi jujur, sepertinya ada jam yang benar-benar buruk pada sistem saat itu.
Dwight Spencer
Solusi ini menurut saya mengandalkan perilaku penyangga tertentu. Saya menduga untuk file yang lebih besar ini dapat rusak karena ketika sedang membaca, file mungkin mulai berubah di bawahnya oleh perintah patch, atau bahkan lebih buruk dengan perintah tee yang akan memotong file. Sebenarnya saya tidak yakin apakah patchtidak akan terpotong juga. Saya pikir menyimpan output ke file lain dan kemudian catke asli akan lebih aman.
akostadinov
jawaban nyata di tempat dari @ william-seekell
akostadinov
11

Tidak mungkin melakukan apa yang Anda inginkan sed. Bahkan versi sedyang mendukung -iopsi untuk mengedit file di tempat melakukan persis apa yang Anda secara eksplisit menyatakan tidak Anda inginkan: mereka menulis ke file sementara dan kemudian mengganti nama file. Tapi mungkin Anda bisa menggunakannya ed. Misalnya, untuk mengubah semua kemunculan fooke bardalam file file.txt, Anda dapat melakukan:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

Sintaks mirip dengan sed, tetapi tentu saja tidak persis sama.

Tapi mungkin Anda harus mempertimbangkan mengapa Anda tidak ingin menggunakan file sementara berganti nama. Bahkan jika Anda tidak memiliki -ipendukung sed, Anda dapat dengan mudah menulis skrip untuk melakukan pekerjaan untuk Anda. Alih-alih sed -i 's/foo/bar/g' file, Anda bisa melakukannya inline file sed 's/foo/bar/g'. Skrip seperti itu sepele untuk ditulis. Sebagai contoh:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

harus memadai untuk sebagian besar kegunaan.

William Pursell
sumber
1
jadi misalnya, bagaimana Anda memberi nama skrip ini dan bagaimana Anda menyebutnya?
amfibi
2
Saya akan menyebutnya inlinedan memohon seperti yang dijelaskan di atas:inline inputfile sed 's/foo/bar/g'
William Pursell
1
wow, edhanya jawaban yang benar-benar ada di tempat. Pertama kali saya butuhkan ed. Terima kasih. Sedikit optimasi adalah dengan menggunakan echo -e ",s/foo/bar/g\012 w"atau echo $',s/foo/bar/g\012 w'jika shell memungkinkannya menghindari trpanggilan ekstra .
akostadinov
2
edtidak benar-benar melakukan modifikasi di tempat. Dari straceoutput, GNU edmembuat file temp, menulis perubahan ke file temp, kemudian menulis ulang seluruh file asli, menjaga tautan keras. Dari trussoutput, Solaris 11 edjuga menggunakan file temp, tetapi mengganti nama file temp ke nama file asli setelah disimpan dengan wperintah, menghancurkan semua tautan keras.
Andrew Henle
@AndrewHenle Itu mengecilkan hati. Yang eddikirimkan dengan makro saat ini (Mojave, apa pun arti jargon pemasarannya) masih mempertahankan hardlink. YMMV
William Pursell
10

Anda bisa menggunakan vi

vi -c '%s/foo/bar/g' my.txt -c 'wq'
mench
sumber
9

sed mendukung pengeditan di tempat. Dari man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Contoh :

Katakanlah Anda memiliki file hello.txtdengan teks:

hello world!

Jika Anda ingin menyimpan cadangan file lama, gunakan:

sed -i.bak 's/hello/bonjour' hello.txt

Anda akan mendapatkan dua file: hello.txtdengan konten:

bonjour world!

dan hello.txt.bakdengan konten lama.

Jika Anda tidak ingin menyimpan salinan, jangan lewati parameter ekstensi.

Sergio A.
sumber
3
OP secara khusus menyebutkan bahwa platformnya tidak mendukung opsi (populer tapi) tidak standar ini.
tripleee
3

Anda tidak menentukan shell apa yang Anda gunakan, tetapi dengan zsh Anda bisa menggunakan =( )konstruk untuk mencapai ini. Sesuatu di sepanjang garis:

cp =(sed ... file; sync) file

=( )mirip dengan >( )tetapi membuat file sementara yang secara otomatis dihapus ketika cpberakhir.

Thor
sumber
3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

Seharusnya menjaga semua hardlink, karena output diarahkan kembali untuk menimpa isi file asli, dan menghindari segala kebutuhan untuk versi sed khusus.

JJM
sumber
3

Jika Anda mengganti jumlah karakter yang sama dan setelah membaca dengan saksama "Di-tempat" mengedit file ...

Anda juga dapat menggunakan operator pengalihan <>untuk membuka file untuk membaca dan menulis:

sed 's/foo/bar/g' file 1<> file

Lihat langsung:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

Dari Manual Referensi Bash → 3.6.10 Membuka Deskriptor File untuk Membaca dan Menulis :

Operator pengalihan

[n]<>word

menyebabkan file yang namanya adalah perluasan kata yang dibuka untuk membaca dan menulis pada deskriptor file n, atau pada deskriptor file 0 jika n tidak ditentukan. Jika file tidak ada, itu dibuat.

fedorqui 'SO berhenti merugikan'
sumber
Ini rusak oleh file. Saya tidak yakin alasan di balik itu. paste.fedoraproject.org/462017
Nehal J Wani
Lihat baris [43-44], [88-89], [135-136]
Nehal J Wani
2
Blogpost ini menjelaskan mengapa jawaban ini tidak boleh digunakan. backreference.org/2011/01/29/in-place-editing-of-files
Nehal J Wani
@NehalJWani Saya sudah lama membaca artikelnya, terima kasih untuk yang sudah hadir! Apa yang tidak saya sebutkan dalam jawaban adalah bahwa ini berfungsi jika ia mengubah jumlah karakter yang sama.
fedorqui 'SO stop harming'
@NehalJWani ini dikatakan, saya minta maaf jika jawaban saya menyebabkan kerusakan pada file Anda. Saya akan memperbaruinya dengan koreksi (atau menghapusnya jika perlu) setelah membaca tautan yang Anda berikan.
fedorqui 'SO berhenti merugikan'
2

Seperti yang dikatakan Moneypenny di Skyfall: "Terkadang cara lama adalah yang terbaik." Kincade mengatakan sesuatu yang serupa di kemudian hari.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Selamat mengedit di tempat. Menambahkan '\ nw \ n' untuk menulis file. Permintaan maaf untuk keterlambatan menjawab permintaan.

Jlettvin
sumber
Bisakah Anda menjelaskan apa fungsinya?
Matt Montag
1
Memanggil ed (1), editor teks dengan beberapa karakter ditulis ke stdin. Sebagai seseorang di bawah usia 30, saya pikir tidak apa-apa bagi saya untuk mengatakan bahwa ed (1) adalah untuk dinosaurus sebagaimana dinosaurus bagi kita. Pokoknya, alih-alih membuka ed dan mengetik hal-hal itu, jlettvin mem-piping karakter dalam menggunakan |. @MattMontag
Ari Sweedler
Jika Anda bertanya apa yang dilakukan perintah, ada dua, diakhiri oleh a \n. [range] s / <old> / <new> / <flag> adalah bentuk perintah pengganti - sebuah paradigma yang masih terlihat di banyak editor teks lainnya (oke, vim, turunan langsung dari ed). Bagaimanapun, gbendera adalah singkatan dari "global", dan berarti "Setiap contoh di setiap baris yang Anda lihat". Rentang 3,5terlihat pada garis 3-5. ,5melihat StartOfFile-5, 3,melihat garis 3-EndOfFile dan ,melihat StartOfFile-EndOfFile. Perintah pengganti global di setiap baris yang menggantikan "false" dengan "true". Kemudian, perintah tulis w,, dimasukkan.
Ari Sweedler
1

Contoh yang sangat bagus. Saya memiliki tantangan untuk mengedit banyak file dan opsi -i tampaknya menjadi satu-satunya solusi yang masuk akal menggunakannya dalam perintah find. Di sini skrip untuk menambahkan "versi:" di depan baris pertama setiap file:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
Robert
sumber