Temukan dan Ganti Di Dalam File Teks dari Perintah Bash

561

Apa cara paling sederhana untuk mencari dan mengganti string input yang diberikan, katakan abc, dan ganti dengan string lain, katakan XYZdalam file /tmp/file.txt?

Saya sedang menulis aplikasi dan menggunakan IronPython untuk menjalankan perintah melalui SSH - tetapi saya tidak tahu Unix dengan baik dan tidak tahu apa yang harus dicari.

Saya telah mendengar bahwa Bash, selain sebagai antarmuka baris perintah, dapat menjadi bahasa scripting yang sangat kuat. Jadi, jika ini benar, saya berasumsi Anda dapat melakukan tindakan seperti ini.

Bisakah saya melakukannya dengan bash, dan skrip apa yang paling sederhana (satu baris) untuk mencapai tujuan saya?

Abu
sumber

Jawaban:

930

Cara termudah adalah menggunakan sed (atau perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Yang akan meminta sed untuk melakukan edit di tempat karena -iopsi. Ini bisa dipanggil dari bash.

Jika Anda benar-benar ingin menggunakan bash saja, maka yang berikut ini bisa berfungsi:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Ini loop atas setiap baris, melakukan substitusi, dan menulis ke file sementara (tidak ingin mengalahkan input). Langkah di akhir hanya bergerak sementara ke nama aslinya.

johnny
sumber
3
Kecuali bahwa memanggil mv cukup banyak sebagai 'non Bash' seperti menggunakan sed. Saya hampir mengatakan hal yang sama dari gema, tetapi itu adalah shell builtin.
langsing
5
Argumen -i untuk sed tidak ada untuk Solaris (dan saya akan berpikir beberapa implementasi lainnya), jadi ingatlah itu. Hanya menghabiskan beberapa menit mencari tahu ...
Panky
2
Catatan untuk diri sendiri: tentang ekspresi reguler sed: s/..../..../ - Substitutedan /g - Global
checksum
89
Catatan untuk pengguna Mac yang mendapatkan invalid command code Ckesalahan ... Untuk penggantian di tempat, BSD sedmemerlukan ekstensi file setelah -iflag karena menyimpan file cadangan dengan ekstensi yang diberikan. Sebagai contoh: sed -i '.bak' 's/find/replace/' /file.txt Anda dapat melewatkan cadangan dengan menggunakan string kosong seperti:sed -i '' 's/find/replace/' /file.txt
Austin
8
Kiat: Jika Anda ingin menggunakan repalce peka huruf besar-kecils/abc/XYZ/gi
Boris D. Teoharov
166

Manipulasi file biasanya tidak dilakukan oleh Bash, tetapi oleh program yang dipanggil oleh Bash, misalnya:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

The -iflag mengatakan itu untuk melakukan penggantian di tempat.

Lihat man perlrununtuk detail lebih lanjut, termasuk cara mengambil cadangan file asli.

Alnitak
sumber
37
Purist di saya mengatakan Anda tidak bisa yakin Perl akan tersedia di sistem. Tapi itu sangat jarang terjadi saat ini. Mungkin saya menunjukkan usia saya.
langsing
3
Bisakah Anda menunjukkan contoh yang lebih kompleks. Sesuatu seperti mengganti "chdir / blah" dengan "chdir / blah2". Saya mencoba perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, tetapi saya terus mendapatkan kesalahan dengan Tidak memiliki ruang antara pola dan kata berikut sudah ditinggalkan pada -e baris 1. Tidak cocok (dalam regex; ditandai dengan <- DI SINI di m / (chdir) () (<- DI SINI ?: \\ / at -e line 1.
CMCDragonkai
@CMCDragonkai Periksa jawaban ini: stackoverflow.com/a/12061491/2730528
Alfonso Santiago
69

Saya terkejut ketika saya menemukan ini ...

Ada replaceperintah yang dikirimkan bersama "mysql-server"paket, jadi jika Anda telah menginstalnya, cobalah:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Lihat man replacelebih lanjut tentang ini.

rayro
sumber
12
Dua hal yang mungkin di sini: a) replaceadalah alat independen yang berguna dan orang-orang MySQL harus melepaskannya secara terpisah dan bergantung padanya b) replacememerlukan sedikit MySQL o_O Either way, menginstal mysql-server untuk mendapatkan penggantian akan menjadi hal yang salah untuk dilakukan :)
Philip Whitehouse
hanya berfungsi untuk mac? di ubuntu saya, saya centos perintah itu tidak ada
paul
1
Itu karena Anda belum mysql-servermenginstal paket. Seperti yang ditunjukkan oleh @rayro, replaceadalah bagian dari itu.
Phius
2
"Peringatan: ganti sudah usang dan akan dihapus dalam versi yang akan datang."
Steven Vachon
1
Berhati-hatilah untuk tidak menjalankan perintah REPLACE di Windows! Pada Windows, perintah REPLACE adalah replikasi file yang cepat. Tidak relevan dengan diskusi ini.
Maor
53

Ini adalah pos lama tetapi bagi siapa pun yang ingin menggunakan variabel seperti @centurian mengatakan tanda kutip tunggal berarti tidak ada yang akan diperluas.

Cara sederhana untuk mendapatkan variabel adalah dengan melakukan penggabungan string karena ini dilakukan dengan menyandingkan di bash, yang berikut ini akan berfungsi:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt
zcourts
sumber
39

Bash, seperti shell lainnya, hanyalah alat untuk mengoordinasikan perintah lain. Biasanya Anda akan mencoba menggunakan perintah UNIX standar, tetapi tentu saja Anda dapat menggunakan Bash untuk memanggil apa pun, termasuk program yang Anda buat sendiri, skrip shell lain, skrip Python dan Perl, dll.

Dalam hal ini, ada beberapa cara untuk melakukannya.

Jika Anda ingin membaca file, dan menulisnya ke file lain, melakukan pencarian / ganti saat Anda pergi, gunakan sed:

sed 's/abc/XYZ/g' <infile >outfile

Jika Anda ingin mengedit file pada tempatnya (seolah membuka file dalam editor, mengeditnya, lalu menyimpannya) berikan instruksi ke editor baris 'ex'

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex seperti vi tanpa mode layar penuh. Anda dapat memberikannya perintah yang sama dengan yang Anda minta di prompt ':' vi.

ramping
sumber
33

Saya menemukan utas ini di antara yang lain dan saya setuju itu berisi jawaban yang paling lengkap sehingga saya juga menambahkan utas saya:

  1. seddan edsangat berguna ... dengan tangan. Lihat kode ini dari @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Ketika batasan saya adalah menggunakannya dalam skrip shell, tidak ada variabel yang dapat digunakan di dalam menggantikan "abc" atau "XYZ". The BashFAQ tampaknya setuju dengan apa yang saya mengerti setidaknya. Jadi, saya tidak bisa menggunakan:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt
    

    tapi, apa yang bisa kita lakukan? Seperti, @Johnny berkata gunakan while read...tapi, sayangnya itu bukan akhir dari cerita. Berikut ini bekerja dengan baik untuk saya:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
    
  3. Seperti yang dapat dilihat jika nullglobdiatur kemudian, itu berperilaku aneh ketika ada string yang mengandung *seperti pada:

    <VirtualHost *:80>
     ServerName www.example.com
    

    yang menjadi

    <VirtualHost ServerName www.example.com

    tidak ada bracket sudut ujung dan Apache2 bahkan tidak bisa memuat.

  4. Penguraian seperti ini harus lebih lambat dari pencarian sekali pakai dan ganti tetapi, seperti yang sudah Anda lihat, ada empat variabel untuk empat pola pencarian berbeda yang bekerja pada satu siklus penguraian.

Solusi yang paling cocok yang bisa saya pikirkan dengan asumsi masalah yang diberikan.

centurian
sumber
12
Dalam (2) Anda - Anda dapat melakukannya sed -e "s/$x/$y/", dan itu akan berhasil. Bukan kutipan ganda. Itu bisa sangat membingungkan jika string dalam variabel itu sendiri mengandung karakter dengan makna khusus. Misalnya jika x = "/" atau x = "\". Ketika Anda mengalami masalah ini, itu mungkin berarti Anda harus berhenti mencoba menggunakan shell untuk pekerjaan ini.
langsing
Hai langsing, saya melihat bahwa Anda juga menentang penggunaan Perl. Apa solusinya? Karena saya sebenarnya ingin mengubah path secara dinamis dalam sebuah file, yang berarti saya memiliki banyak / dalam string!
Mahdi
20

Anda bisa menggunakan sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Gunakan -iuntuk "abaikan case" jika Anda tidak yakin teksnya ditemukan abcatau ABCatau AbC, dll.

Anda dapat menggunakan finddan sedjika Anda tidak tahu nama file Anda:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Temukan dan ganti semua file Python:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;
MMParvin
sumber
12

Anda juga dapat menggunakan edperintah untuk melakukan pencarian dalam file dan mengganti:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Lihat lebih banyak di " Mengedit file melalui skrip denganed ".

Manusia Timah
sumber
3
Solusi ini independen dari ketidakcocokan GNU / FreeBSD (Mac OSX) (tidak seperti sed -i <pattern> <filename>). Sangat bagus!
Peterino
11

Jika file yang sedang Anda kerjakan tidak terlalu besar, dan menyimpannya sementara dalam suatu variabel tidak menjadi masalah, maka Anda dapat menggunakan subtitusi Bash string pada seluruh file sekaligus - tidak perlu untuk membahasnya baris demi baris:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

Seluruh isi file akan diperlakukan sebagai satu string panjang, termasuk linebreak.

XYZ dapat berupa variabel misalnya $replacement, dan satu keuntungan dari tidak menggunakan sed di sini adalah Anda tidak perlu khawatir bahwa pencarian atau penggantian string mungkin berisi karakter pembatas pola sed (biasanya, tetapi tidak harus, /). Kerugiannya adalah tidak bisa menggunakan ekspresi reguler atau operasi sed yang lebih canggih.

Johnraff
sumber
Adakah kiat untuk menggunakan ini dengan karakter tab? Untuk beberapa alasan skrip saya tidak menemukan apa pun dengan tab setelah berganti dari sed dengan banyak pelarian ke metode ini.
Brian Hannay
2
Jika Anda ingin meletakkan tab di string yang Anda ganti, Anda dapat melakukannya dengan sintaks "dollared single quotes" Bash, jadi tab diwakili oleh $ '\ t', dan Anda dapat melakukan $ echo 'tab' $ '\' dipersiapkan '> testfile; $ file_contents = $ (<testfile); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABpisah `
johnraff
5

Coba perintah shell berikut:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
J Ajay
sumber
4
Ini adalah jawaban yang sangat baik untuk "bagaimana saya secara tidak sengaja semua file di semua subdirektori juga" tetapi sepertinya tidak seperti yang ditanyakan di sini.
tripleee
Sintaks ini tidak akan berfungsi untuk versi BSD sed, gunakan sed -i''saja.
kenorb
5

Untuk mengedit teks dalam file secara non-interaktif, Anda memerlukan editor teks in-place seperti vim.

Berikut adalah contoh sederhana cara menggunakannya dari baris perintah:

vim -esnc '%s/foo/bar/g|:wq' file.txt

Ini setara dengan jawaban @slim dari mantan editor yang pada dasarnya adalah hal yang sama.

Berikut adalah beberapa excontoh praktis.

Mengganti teks foodengan bardalam file:

ex -s +%s/foo/bar/ge -cwq file.txt

Menghapus jejak spasi putih untuk banyak file:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Pemecahan masalah (ketika terminal macet):

  • Tambahkan -V1param untuk menampilkan pesan verbose.
  • Angkatan berhenti oleh: -cwq!.

Lihat juga:

kenorb
sumber
Ingin melakukan penggantian secara interaktif. Karenanya mencoba "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Tapi terminal macet sekarang. Bagaimana kita akan membuat penggantian secara interaktif tanpa bash shell berperilaku aneh.
vineeshvs
Untuk debug, tambahkan -V1, untuk memaksa berhenti, gunakan wq!.
kenorb
2

Anda juga bisa menggunakan python dalam skrip bash. Saya tidak terlalu sukses dengan beberapa jawaban teratas di sini, dan menemukan ini berfungsi tanpa perlu pengulangan:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()
mithith
sumber
1

Anda dapat menggunakan perintah rpl. Misalnya Anda ingin mengubah nama domain di seluruh proyek php.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Ini bukan penyebab yang jelas, tetapi sangat cepat dan bermanfaat. :)

zalex
sumber