Ubah beberapa file

194

Perintah berikut ini dengan benar mengubah isi 2 file.

sed -i 's/abc/xyz/g' xaa1 xab1 

Tapi yang perlu saya lakukan adalah mengubah beberapa file seperti itu secara dinamis dan saya tidak tahu nama file tersebut. Saya ingin menulis perintah yang akan membaca semua file dari direktori saat ini dimulai dengan xa*dan sedharus mengubah konten file.

shantanuo
sumber
61
Maksudmu sed -i 's/abc/xyz/g' xa*?
Paul R
3
Jawaban di sini tidak cukup. Lihat unix.stackexchange.com/questions/112023/...
Isaac

Jawaban:

135

Lebih baik:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

karena tidak ada yang tahu berapa banyak file di sana, dan mudah untuk melanggar batas baris perintah.

Inilah yang terjadi ketika ada terlalu banyak file:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#
lenik
sumber
18
Jika ada banyak file, Anda akan melanggar batas baris perintah di forperintah. Untuk melindungi diri dari hal itu, Anda harus menggunakanfind ... | xargs ...
glenn jackman
1
Saya tidak tahu implementasinya, tetapi pola "xa *" memang harus diperluas di beberapa titik. Apakah shell melakukan ekspansi secara berbeda untuk fordaripada yang dilakukannya untuk echoatau grep?
glenn jackman
4
lihat jawaban yang diperbarui. jika Anda memerlukan info lebih lanjut, silakan, ajukan pertanyaan resmi, sehingga orang dapat membantu Anda.
Lenik
5
Dalam perintah sed, Anda harus menggunakan "$i"alih-alih $imenghindari pemisahan kata pada nama file dengan spasi. Kalau tidak, ini sangat bagus.
Wildcard
4
Mengenai daftar, saya percaya perbedaannya adalah itu foradalah bagian dari sintaks bahasa, bahkan bukan hanya builtin. Sebab sed -i 's/old/new' *, perluasan *SEMUA harus dilewatkan sebagai argumen untuk sed, dan saya cukup yakin ini harus terjadi sebelum sedproses bahkan dapat dimulai. Menggunakan forloop, argumen penuh (ekspansi *) tidak pernah dilewatkan sebagai perintah, hanya disimpan dalam memori shell dan diulangi. Saya tidak punya referensi untuk ini sama sekali, sepertinya ada bedanya. (Saya ingin mendengar dari seseorang yang lebih berpengetahuan ...)
Wildcard
165

Saya terkejut tidak ada yang menyebutkan argumen -exec untuk ditemukan, yang ditujukan untuk jenis penggunaan-kasus ini, meskipun akan memulai proses untuk setiap nama file yang cocok:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

Atau, orang dapat menggunakan xargs, yang akan meminta lebih sedikit proses:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

Atau lebih mudah gunakan + varian exec daripada ;di find untuk memungkinkan find menyediakan lebih dari satu file per panggilan subproses:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +
ealfonso
sumber
7
Saya harus memodifikasi perintah dalam jawaban ini seperti itu: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;itu adalah lokasi untuk perintah find ./dan sepasang tanda kutip tunggal -iuntuk OSX.
shelbydz
Perintah find berfungsi seperti yang disediakan oleh ealfonso, ./sama dengan .dan setelah -ihanya memiliki parameter backupsuffix.
uhausbrand
The -execpilihan untuk menemukan bersama dengan {} +cukup untuk memecahkan masalah seperti yang dinyatakan, dan harus baik untuk sebagian besar kebutuhan. Tetapi xargsmerupakan pilihan yang lebih baik secara umum karena juga memungkinkan pemrosesan paralel dengan -popsi. Saat ekspansi gumpalan Anda cukup besar untuk melebihi panjang baris perintah Anda, Anda kemungkinan juga akan mendapat manfaat dari peningkatan kecepatan selama menjalankan berurutan.
Amit Naidu
78

Anda bisa menggunakan grep dan sed secara bersamaan. Ini memungkinkan Anda untuk mencari subdirektori secara rekursif.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)
Raj
sumber
3
Bonus dari metode ini bagi saya adalah saya bisa menyelinap masuk grep -vuntuk menghindari folder gitgrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne
solusi terbaik untuk mac!
Marquis Blount
30

Perintah-perintah itu tidak akan berfungsi di default sedyang menyertai Mac OS X.

Dari man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

Mencoba

sed -i '.bak' 's/old/new/g' logfile*

dan

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Keduanya bekerja dengan baik.

funroll
sumber
2
@sumek Berikut adalah contoh sesi terminal pada OS X yang menunjukkan sed yang menggantikan semua kejadian: GitHub Gist
funroll
Saya menggunakan ini untuk mengganti dua baris berbeda di semua file konfigurasi situs web saya dengan satu-liner di bawah ini. sed -i.bak "s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" situs-available / * Jangan lupa untuk menghapus file * .bak ketika Anda selesai melakukan file Demi kebersihan sistem.
Josiah
19

@PaulR memposting ini sebagai komentar, tetapi orang-orang harus melihatnya sebagai jawaban (dan jawaban ini paling cocok untuk kebutuhan saya):

sed -i 's/abc/xyz/g' xa*

Ini akan bekerja untuk jumlah file sedang, mungkin dalam urutan puluhan, tetapi mungkin tidak pada urutan jutaan .

palswim
sumber
Asumsikan Anda memiliki garis miring ke depan dalam penggantian Anda. Contoh lain dengan filepath sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
Léo Léopold Hertz 준영
10

Cara lain yang lebih fleksibel adalah dengan menggunakan find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')
dkinzer
sumber
1
output dari perintah find itu diperluas, jadi ini tidak mengatasi masalah. Sebagai gantinya Anda harus menggunakan -exec
ealfonso
@erjoalgo ini berfungsi karena perintah sed dapat menangani beberapa file input. Perluasan perintah find sebenarnya diperlukan untuk membuatnya bekerja.
dkinzer
ini berfungsi selama jumlah file tidak mendorong ke batas baris perintah.
ealfonso
Batas itu hanya bergantung pada sumber daya memori yang tersedia untuk mesin dan itu persis sama dengan batas untuk exec.
dkinzer
4
Itu tidak benar. Dalam perintah Anda di atas, $ (find. ...) akan diperluas menjadi satu perintah, yang bisa sangat lama jika ada banyak file yang cocok. Jika terlalu panjang (misalnya di sistem saya batasnya sekitar 2097152 karakter) Anda mungkin mendapatkan kesalahan: "Daftar argumen terlalu panjang" dan perintah akan gagal. Silakan google kesalahan ini untuk mendapatkan latar belakang tentang ini.
ealfonso
2

Saya menggunakan finduntuk tugas serupa. Ini cukup sederhana: Anda harus menyampaikannya sebagai argumen untuk sedseperti ini:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

Dengan cara ini Anda tidak perlu menulis loop yang rumit, dan mudah dilihat, file mana yang akan Anda ubah, jalankan saja find sebelum Anda jalankan sed.

Bluesboy
sumber
1
Ini persis sama dengan jawaban @kinkin's .
Tuan Tao
0

kamu bisa membuat

' xxxx ' teks yang Anda cari dan akan menggantinya dengan ' yyyy '

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'
Mohamed Galal
sumber
0

Jika Anda dapat menjalankan skrip, inilah yang saya lakukan untuk situasi serupa:

Menggunakan kamus / hashMap (array asosiatif) dan variabel untuk sedperintah, kita bisa mengulang array untuk mengganti beberapa string. Termasuk wildcard di name_patternakan memungkinkan untuk mengganti di tempat dalam file dengan pola (ini bisa menjadi sesuatu seperti name_pattern='File*.txt') di direktori tertentu ( source_dir). Semua perubahan ditulis di logfiledalamdestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Pertama, array pasangan dideklarasikan, masing-masing pasangan adalah string pengganti, kemudian WHAT1akan diganti untuk FOR1dan OTHER_string_to replaceakan diganti untuk string replaceddalam file File.txt. Dalam loop array dibaca, anggota pertama dari pasangan tersebut diambil sebagai replace_what=$idan yang kedua sebagai replace_for=$j. The findpencarian perintah dalam direktori nama file (yang mungkin berisi wildcard) dan sed -iMenggantikan perintah dalam file yang sama (s) apa yang didefinisikan sebelumnya. Akhirnya saya menambahkangrep arahan ulang ke logfile untuk mencatat perubahan yang dibuat dalam file.

Ini bekerja untuk saya GNU Bash 4.3 sed 4.2.2dan berdasarkan jawaban VasyaNovikov untuk Loop over tuple di bash .

Lejuanjowski
sumber