Bagaimana saya bisa menggunakan variabel dalam LHS dan RHS substitusi sed?

61

Aku ingin melakukan:

cat update_via_sed.sh | sed 's/old_name/new_name/' > new_update_via_sed.sh

dalam program saya.

Tetapi saya ingin menggunakan variabel, misalnya

old_run='old_name_952'
new_run='old_name_953'

Saya telah mencoba menggunakannya tetapi substitusi tidak terjadi (tidak ada kesalahan). Saya telah mencoba:

cat update_via_sed.sh | sed 's/old_run/new_run/'
cat update_via_sed.sh | sed 's/$old_run/$new_run/'
cat update_via_sed.sh | sed 's/${old_run}/${new_run}/'
Michael Durrant
sumber
2
Anda dapat menemukan jawabannya di Gunakan parameter dalam argumen perintah .
manatwork

Jawaban:

45

Anda bisa melakukannya:

sed "s/$old_run/$new_run/" < infile > outfile

Tapi berhati-hatilah bahwa $old_runakan diambil sebagai ekspresi reguler dan karakter khusus yang variabel mengandung, seperti /atau .harus harus melarikan diri . Demikian pula, dalam $new_run, karakter &dan \perlu diperlakukan khusus dan Anda harus melarikan diri /dan karakter baris baru di dalamnya.

Jika konten $old_runatau $new_runtidak di bawah kendali Anda, maka sangat penting untuk melakukan pelolosan itu, atau sebaliknya kode tersebut merupakan kerentanan injeksi kode .

Stéphane Chazelas
sumber
3
Saya menggunakan kutipan tunggal dalam sed sed, beralih ke tanda kutip ganda dan itu berfungsi file. Luar biasa.
Aakash
bukan itu sedsaja tidak akan menggunakan regex tapi kan sed -E?
Kiwy
@ Kyiwy, tidak. -eadalah untuk menentukan sed e Xpression , sehingga Anda dapat melewati lebih dari satu (seperti dalam sed -e exp1 -e exp2) atau kombinasi dari file dan ekspresi ( sed -f file -e exp). Anda mungkin bingung dengan -Eyang mana dengan beberapa implementasi, memberitahu sed untuk menggunakan ERE bukannya BRE.
Stéphane Chazelas
Saya salah ketik, tapi OK, itu menjelaskan mengapa saya memiliki begitu banyak masalah menggunakan sed tanpa -Esaya hanya menguasai regex diperpanjang bukan yang biasa. Terima kasih untuk penjelasannya.
Kiwy
@ Kiwy, lihat juga Tanya Jawab terkait untuk opsi lebih lanjut yang didukung oleh beberapa sedimplementasi untuk sintaksis ekspresi yang lebih reguler.
Stéphane Chazelas
31

Ini berhasil:

cat update_via_sed.sh | sed 's/'"$old_run"'/'"$new_run"'/'

Karena saya ingin 'menggunakan kembali' file yang sama, saya sebenarnya menggunakan ini untuk siapa saja yang menginginkan pendekatan yang sama:

cat update_via_sed.sh | sed 's/'"$old_run"'/'"$new_run"'/' > new_update; mv -f new_update update_via_sed.sh

Di atas membuat file baru kemudian menghapus file saat ini daripada mengganti nama file baru menjadi file saat ini.

Michael Durrant
sumber
4
Anda tidak perlu kucing, mv, atau ekstra '' kecuali Anda akan memiliki karakter khusus di dalamnya. Jadi begitu sed -i s/"$old"/"$new"/ update_via_sed.sh. Namun perlu diketahui bahwa ini tidak bekerja untuk nilai lama dan baru. mis. itu tidak boleh mengandung / dll, karena $ old masih berupa pencarian dan $ new pola penggantian di mana beberapa karakter memiliki arti khusus.
frostschutz
1
sed -i "s/$old/$new/"juga ok (dengan tindakan pencegahan di atas). sedbiasanya menganggap pemisah sebagai karakter pertama setelah sperintah - yaitu jika variabel Anda berisi garis miring /tetapi tidak memungkinkan katakan pada ( @), Anda dapat menggunakan "s @ $ old @ $ new @".
peterph
22

Anda bisa menggunakan variabel dalam sed selama itu dalam kuotasi ganda (bukan kuotasi tunggal):

sed "s/$var/r_str/g" file_name >new_file

Jika Anda memiliki garis miring ( /) dalam variabel, maka gunakan pemisah yang berbeda seperti di bawah ini

sed "s|$var|r_str|g" file_name >new_file
mani
sumber
1
+1 untuk menyebutkan perlu menggunakan tanda kutip ganda. Itu masalah saya.
speckledcarp
1
Persis apa yang saya cari, termasuk penggunaan |seperti dalam kasus saya, variabel berisi path sehingga ada /di dalamnya
yorch
Untuk beberapa alasan pipa |bekerja untuk saya di MacOS 10.14, tetapi pemisah lainnya suka @atau #tidak. Saya memiliki garis miring ke depan dalam env var saya juga.
davidenke
21

'in-place' sed (usng the -i flag) adalah jawabannya. Berkat peterph.

sed -i "s@$old@$new@" file
Michael Durrant
sumber
2
Anda memiliki kutipan di tempat yang salah. kutipan harus berada di sekitar variabel, bukan s@(yang tidak khusus untuk shell dengan cara apa pun). Yang terbaik adalah memasukkan segala sesuatu di dalam tanda kutip seperti sed -i "s@$old@$new@" file. Perhatikan bahwa -iini bukan sedopsi standar .
Stéphane Chazelas
bagaimana saya bisa lolos $dalam perintah ini. Seperti, saya akan berubah $xxxsecara keseluruhan dengan nilai variabel $JAVA_HOME. Saya mencoba sed -i "s@\$xxx@$JAVA_HOME@" file, tetapi kesalahan mengatakanno previous regular expression
hakunami
3

man bash memberikan ini tentang kutipan tunggal

Menutup karakter dalam tanda kutip tunggal mempertahankan nilai literal setiap karakter dalam tanda kutip. Kutipan tunggal mungkin tidak terjadi di antara tanda kutip tunggal, bahkan ketika didahului oleh garis miring terbalik.

Apa pun yang Anda ketikkan pada baris perintah, bash menafsirkannya dan kemudian mengirimkan hasilnya ke program yang seharusnya dikirim. Dalam kasus ini, jika Anda menggunakan sed 's/$old_run/$new_run/', bash pertama kali melihat, bash sedmengenalinya sebagai hadiah yang dapat dieksekusi dalam $PATHvariabel . The sedexecutable membutuhkan masukan. Bash mencari input dan menemukan 's/$old_run/$new_run/'. Kutipan tunggal mengatakan bash untuk tidak menafsirkan konten di dalamnya dan meneruskannya apa adanya. Jadi, bash lalu berikan mereka sed. Sed memberikan kesalahan karena $dapat terjadi hanya pada akhir baris.

Alih-alih jika kita menggunakan tanda kutip ganda, yaitu, "s/$old_run/$new_run/"maka bash melihat ini dan menafsirkannya $old_runsebagai nama variabel dan membuat substitusi (fase ini disebut ekspansi variabel). Ini memang yang kami butuhkan.

Tapi, Anda harus berhati-hati menggunakan tanda kutip ganda karena, mereka ditafsirkan pertama kali oleh bash dan kemudian diberikan kepada sed. Jadi, beberapa simbol seperti `harus diloloskan sebelum menggunakannya.

nitishch
sumber
1

Beresiko kurang ajar - sudahkah Anda pertimbangkan perl.

Yang - antara lain - cukup bagus dalam melakukan operasi 'sed style', tetapi juga pemrograman yang lebih lengkap.

Ini terlihat seperti dalam contoh Anda, apa yang Anda benar-benar coba lakukan adalah kenaikan nomor.

Jadi bagaimana dengan:

#!/usr/bin/env perl
use strict;
use warnings 'all'; 

while ( <DATA> ) {
    s/old_name_(\d+)/"old_name_".($1+1)/e;
    print;
}

__DATA__
old_name_932

atau sebagai one liner - sed style:

perl -pe 's/old_name_(\d+)/"old_name_".($1+1)/e'
Sobrique
sumber
1
$ echo $d1 | sed -i 's/zkserver1/'${d1}'/g' deva_file
sed: -e expression #1, char 26: unterminated `s' command 

Dalam $d1saya menyimpan nilai

Saya tidak bisa mengganti nilai dari variabel, jadi saya mencoba perintah berikut ini berfungsi

echo $d1 | sed -i 's/zkserver1/'"${d1}"'/g' deva_file

tidak ada tanda kutip ganda "${d1}", itulah kesalahannya

dewa
sumber
-2

Diasumsikan $old_rundan $new_runsudah ditetapkan:

cat update_via_sed.sh | eval $(print "sed 's/$old_run/$new_run/g'")

Ini menunjukkan perubahan di layar. Anda dapat mengarahkan output ke file jika perlu.

Orlando
sumber
-2

Untuk menggunakan variabel dalam sed, gunakan tanda kutip ganda "", untuk menyertakan variabel, dalam pola yang Anda gunakan. Untuk misalnya: a = "Hai" Jika Anda ingin menambahkan variabel 'a' ke suatu pola, Anda dapat menggunakan yang berikut: sed -n "1, / pattern" $ {a} "pattern / p" nama file

Sher
sumber
2
Perluasan $atidak dikutip di sini, yang berarti bahwa setiap spasi dalam nilainya akan meminta pemisahan kata dalam shell.
Kusalananda
-2

Anda juga dapat menggunakan Perl:

perl -pi -e ""s/$val1/$val2/g"" file
Samba
sumber
Pertimbangkan variabel yang mungkin memiliki spasi dalam nilainya. String kosong di sekitar ekspresi Perl ( "") tidak akan melakukan apa pun.
Kusalananda
-2

putus tali

bad => linux lihat string $ old_run:

sed 's/$old_run/$new_run/'

good => linux lihat variabel $ old_run:

sed 's/'$old_run'/'$new_run'/'
marcdahan
sumber
1
Dan jika $old_runatau $new_runmengandung spasi?
Kusalananda