Cara mengambil dan mengganti

252

Saya perlu secara rekursif mencari string yang ditentukan dalam semua file dan subdirektori dalam direktori dan mengganti string ini dengan string lain.

Saya tahu bahwa perintah untuk menemukannya mungkin terlihat seperti ini:

grep 'string_to_find' -r ./*

Tetapi bagaimana saya bisa mengganti setiap instance string_to_finddengan string lain?

Billtian
sumber
Saya tidak percaya grep bisa melakukan ini (saya bisa saja salah). Cara yang lebih mudah adalah dengan menggunakan sed atau perl untuk melakukan penggantian
Memento Mori
2
Coba gunakansed -i 's/.*substring.*/replace/'
Eddy_Em
2
@ Eddy_Em Itu akan mengganti seluruh baris dengan ganti. Anda perlu menggunakan pengelompokan untuk menangkap bagian dari garis sebelum dan sesudah substring dan kemudian meletakkannya di baris pengganti. sed -i 's/\(.*\)substring\(.*\)/\1replace\2/'
JStrahl

Jawaban:

249

Pilihan lain adalah menggunakan find dan kemudian melewati sed.

find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' {} \;
rezizter
sumber
34
Pada OS X 10.10 Terminal, string ekstensi ke parameter -idiperlukan. Misalnya, find /path/to/files -type f -exec sed -i "" "s/oldstring/new string/g" {} \;Bagaimanapun, memberikan string kosong masih membuat file cadangan tidak seperti yang dijelaskan dalam manual ...
Eonil
10
Mengapa saya mendapatkan "sed: RE error: sequence byte ilegal". Dan ya, saya menambahkan -i ""untuk OS X. Ini berfungsi sebaliknya.
taco
2
Saya memiliki masalah urutan byte ilegal di macOS 10.12, dan pertanyaan / jawaban ini memecahkan masalah saya: stackoverflow.com/questions/19242275/… .
abeboparebop
3
Ini menyentuh setiap file sehingga waktu file diubah; dan mengubah ujung garis dari CRLFke LFpada Windows.
jww
183

Saya mendapat jawabannya.

grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'
Billtian
sumber
14
Ini akan memindai file yang cocok dua kali ... sekali dengan grepdan sekali lagi dengan sed. Menggunakan findmetode lebih efisien tetapi metode yang Anda sebutkan ini berfungsi.
cmevoli
41
Pada OS X Anda harus mengubah sed -i 's/str1/str2/g'agar sed -i "" 's/str1/str2/g'ini berfungsi.
jdf
6
@ cmevoli dengan metode ini, grepmemeriksa semua file dan sedhanya memindai file yang cocok grep. Dengan findmetode di jawaban lain, findpertama-tama daftar semua file, dan kemudian sedakan memindai semua file dalam direktori itu. Jadi metode ini belum tentu lebih lambat, tergantung pada berapa banyak kecocokan yang ada dan perbedaan kecepatan pencarian antara sed, grepdan find.
joelostblom
4
OTOH dengan cara ini memungkinkan Anda PREVIEW apa yang ditemukan grep SEBELUM benar-benar diganti, sangat mengurangi risiko kegagalan, terutama untuk regex n00bs seperti saya
Lennart Rolland
2
Ini juga berguna ketika penggantian grep Anda lebih pintar dari pada sed. Misalnya ripgrep mematuhi .gitignore sementara sed tidak.
user31389
43

Anda bahkan dapat melakukannya seperti ini:

Contoh

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

Ini akan mencari string ' windows ' di semua file relatif terhadap direktori saat ini dan mengganti ' windows ' dengan ' linux ' untuk setiap kemunculan string di setiap file.

Dulith De Costa
sumber
2
Ini grephanya berguna jika ada file yang tidak boleh dimodifikasi. Menjalankan sedsemua file akan memperbarui tanggal modifikasi file tetapi membiarkan konten tidak berubah jika tidak ada kecocokan.
tripleee
@ tripleee: Hati-hati dengan ... tapi [sed] biarkan isinya tidak berubah jika tidak ada yang cocok " . Saat menggunakan -i, saya yakin sedmengubah waktu file dari setiap file yang disentuhnya, meskipun isinya tidak berubah. sedJuga mengonversi akhiran baris. Saya tidak menggunakan sedWindows dalam repo Git karena semua CRLFdiubah menjadi LF.
jww
Perintah ini membutuhkan "" setelah -i untuk menyatakan bahwa tidak ada file cadangan yang akan dibuat setelah substitusi di tempat berlangsung, setidaknya di macosx. Periksa halaman manual untuk detailnya. Jika Anda ingin cadangan, ini adalah tempat Anda meletakkan ekstensi file yang akan dibuat.
spinyBabbler
31

Ini bekerja paling baik untuk saya di OS X:

grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi

Sumber: http://www.praj.com.au/post/23691181208/grep-replace-text-string-in-files

Marc Juchli
sumber
ini sempurna! juga bekerja dengan ag:ag "search" -l -r . | sort | uniq | xargs perl -e 's/search/replace' -pi
@sebastiankeller Perintah Perl Anda tidak memiliki garis miring terakhir, yang merupakan kesalahan sintaksis.
tripleee
3
Mengapa bagian sort -ugenap ini? Dalam keadaan apa yang Anda harapkan grep -rluntuk menghasilkan nama file yang sama dua kali?
tripleee
5

Solusi lain mencampur sintaks regex. Untuk menggunakan perl / pola PCRE untuk kedua pencarian dan mengganti, dan hanya file proses pencocokan, karya ini cukup baik:

grep -rlZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;'

di mana match1dan match2biasanya identik tetapi match1dapat disederhanakan untuk menghapus fitur yang lebih canggih yang hanya relevan dengan substitusi, misalnya menangkap grup.

Terjemahan: grepsecara rekursif dan daftar file yang cocok dengan pola PCRE ini, dipisahkan oleh nul untuk melindungi karakter khusus apa pun dalam namafile, lalu pipa nama file xargsyang mengharapkan daftar yang dipisahkan nul, tetapi tidak akan melakukan apa pun jika tidak ada nama yang diterima, dan perluntuk mengganti garis tempat pertandingan ditemukan.

Tambahkan Isakelar grepuntuk mengabaikan file biner. Untuk case-sensitive matching, drop iberalih dari grep, dan ibendera yang melekat pada ekspresi substitusi, tetapi tidak dengan imengaktifkan perlitu sendiri.

Walf
sumber
Perl sendiri cukup mampu mengulang struktur file. Bahkan, ada alat find2perlyang dikirimkan dengan Perl yang melakukan hal semacam ini tanpa xargstipu daya.
tripleee
@tripleee findtidak mencari konten file, dan intinya adalah hanya memproses file yang cocok tanpa menulis program Perl.
Walf
Ini adalah solusi yang bagus untuk Windows, karena menghindari masalah solusi berbasis sed 'dari mengubah akhir baris. Terima kasih!
JamHandy
4

Biasanya tidak dengan grep, melainkan dengan sed -i 's/string_to_find/another_string/g'atau perl -i.bak -pe 's/string_to_find/another_string/g'.

minopret
sumber
3

Berhati-hatilah saat menggunakan finddan seddalam repo git! Jika Anda tidak mengecualikan file biner Anda bisa berakhir dengan kesalahan ini:

error: bad index file sha1 signature 
fatal: index file corrupt

Untuk mengatasi kesalahan ini, Anda harus mengembalikan seddengan mengganti new_stringdengan Anda old_string. Ini akan mengembalikan string yang diganti, sehingga Anda akan kembali ke awal masalah.

Cara yang benar untuk mencari string dan menggantinya adalah dengan melompat finddan menggunakannya grepsebagai gantinya untuk mengabaikan file biner:

sed -ri -e "s/old_string/new_string/g" $(grep -Elr --binary-files=without-match "old_string" "/files_dir")

Kredit untuk @hobs

tsveti_iko
sumber
1

Inilah yang akan saya lakukan:

find /path/to/dir -type f -iname "*filename*" -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

ini akan mencari semua file yang mengandung filenamenama file di bawah /path/to/dir, daripada untuk setiap file yang ditemukan, mencari baris dengansearchstring dan ganti olddengan new.

Padahal jika Anda ingin menghilangkan mencari file tertentu dengan filename string dalam nama file, daripada cukup lakukan:

find /path/to/dir -type f -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

Ini akan melakukan hal yang sama di atas, tetapi untuk semua file yang ditemukan di bawah /path/to/dir.

tinnick
sumber
0

Pilihan lain adalah menggunakan perl dengan globstar.

Mengaktifkan shopt -s globstardi .bashrc(atau di mana pun) Anda memungkinkan** pola gumpalan untuk mencocokkan semua sub-direktori dan file secara rekursif.

Dengan demikian menggunakan perl -pXe 's/SEARCH/REPLACE/g' -i **rekursif akan menggantikan SEARCHdenganREPLACE .

Itu -X flag mengatakan perl untuk "menonaktifkan semua peringatan" - yang berarti bahwa itu tidak akan mengeluh tentang direktori.

The globstar juga memungkinkan Anda untuk melakukan hal-hal seperti sed -i 's/SEARCH/REPLACE/g' **/*.extjika Anda ingin mengganti SEARCHdengan REPLACEsemua file anak dengan ekstensi .ext.

GuiltyDolphin
sumber
"Pilihan lain adalah menggunakan perl dengan globstar ..." - Tidak pada mesin Posixy, seperti Solaris. Itu sebabnya saya secara khusus mencari grepdan sed.
jww