Cara melewatkan variabel yang mengandung garis miring ke sed

100

Bagaimana Anda meneruskan variabel yang mengandung garis miring sebagai pola sed?

Misalnya, jika saya memiliki variabel berikut:

var="/Users/Documents/name/file"

Saya ingin meneruskannya sedsebagai:

sed "s/$var/replace/g" "$file"

Namun saya mendapatkan kesalahan. Bagaimana saya bisa menghindari masalah ini?

buydadip
sumber

Jawaban:

193

Gunakan pembatas ekspresi reguler yang sedmemungkinkan Anda menggunakan pembatas apa pun ( termasuk karakter kontrol ):

sed "s~$var~replace~g" $file
anubhava.dll
sumber
2
Tidak berhasil. Saya mencoba sed -i "s ~ blah ~ $ var ~ g file" dan mendapatkan: "sed -e expression # 1, char 70: unterminated` s 'command". Saya telah mengonfirmasi bahwa pelakunya adalah garis miring di $ var. Varian dari solusi ini ada di mana-mana dan tidak ada yang berfungsi. Apakah sed baru saja diperbarui? Saya menggunakan v4.2.2 di Ubuntu 14.04.4 LTS.
Paul Ericson
Itu tergantung pada berapa nilai$var
anubhava
1
Tidak berfungsi dengan ~ seperti yang diamati @PaulEricson, tetapi masih berfungsi dengan |
Kenny Ho
1
"~" Terakhir (setelah g) tampaknya tidak diperlukan, setidaknya untuk AWS Amazon Linux (berbasis CentOS)
Saúl Martínez Vidals
1
Anda menyelamatkan hari saya
pengguna7131571
62

Jawaban bash murni: gunakan perluasan parameter untuk menghilangkan garis miring terbalik di setiap garis miring dalam variabel:

var="/Users/Documents/name/file"
sed "s/${var//\//\\/}/replace/g" $file
glenn jackman
sumber
1
Ini cara yang baik, karena Anda tidak selalu tahu karakter apa yang mengandung variabel. Jadi, Anda selalu dapat keluar dari pembatas sed jika ada keraguan.Contoh: sed "s: $ {var //: / \\:}: replace: g" $ file
Stephane L
Cantik. Membuat beberapa skrip penggantian nama saya jauh lebih bersih.
Cloud
Mempelajari sesuatu yang indah hari ini, terima kasih. Harus menjadi jawaban yang diterima.
Steve Kehlet
6
Beberapa kejelasan tentang keberadaan pola yang digunakan di sini: ${parameter/pattern/string}. Jadi, dalam hal ini, parameternya adalah var, polanya adalah /\/, dan stringnya adalah \\/. Semua contoh pola diganti karena pola dimulai dengan a /.
Josh
1
@ Astaga, dan garis miring pada pola sebenarnya bukan bagian dari pola yang akan diganti.
glenn jackman
32

Cara lain untuk melakukannya, meskipun lebih jelek dari jawaban anubhava, adalah dengan mengosongkan semua garis miring terbalik dengan varmenggunakan sedperintah lain :

var=$(echo "$var" | sed 's/\//\\\//g')

kemudian, ini akan berhasil:

sed "s/$var/replace/g" $file
buydadip
sumber
12

Menggunakan /in sed sebagai pembatas akan bertentangan dengan garis miring di variabel saat diganti dan akibatnya Anda akan mendapatkan error. Salah satu cara untuk mengelaknya adalah dengan menggunakan pembatas lain yang unik dari semua karakter yang ada di variabel itu.

var="/Users/Documents/name/file"

Anda dapat menggunakan karakter octothorpe yang sesuai dengan acara (atau karakter lain yang bukan /untuk kemudahan penggunaan)

sed "s#$var#replace#g" 

atau

sed 's#$'$var'#replace#g'

ini cocok jika variabel tidak mengandung spasi

atau

sed 's#$"'$var'"#replace#g'

Lebih bijak untuk menggunakan yang di atas karena kami tertarik untuk mengganti apa pun yang ada di variabel itu hanya dibandingkan dengan mengutip ganda seluruh perintah yang dapat menyebabkan shell Anda menginterpretasikan karakter apa pun yang mungkin dianggap sebagai karakter shell khusus ke shell.

repzero
sumber
Tidak berhasil. Saya mencoba sed -i "s ~ blah ~ $ var ~ g file" dan mendapatkan: "sed -e expression # 1, char 70: unterminated` s 'command". Saya telah mengonfirmasi bahwa pelakunya adalah garis miring di $ var. Varian dari solusi ini ada di mana-mana dan tidak ada yang berfungsi. Apakah sed baru saja diperbarui? Saya menggunakan v4.2.2 di Ubuntu 14.04.4 LTS.
Paul Ericson
@PaulEricson Saya tidak dapat melakukan repro ini di Ubuntu mana pun yang tersedia untuk saya. Jika $varberisi ~, Anda jelas harus memilih pembatas yang berbeda. Kesalahan "belum diselesaikan" terdengar seperti Anda $varmengandung baris baru; Anda mungkin bisa memperbaikinya dengan meng-escape setiap baris baru dengan garis miring terbalik dalam nilainya, tetapi menurut saya ini tidak sepenuhnya portabel. Ini pada umumnya tidak terkait dengan masalah garis miring dalam nilai.
tripleee
@PaulEricson Oh dan kutipan Anda salah, Anda tidak boleh memiliki nama file di dalam tanda kutip. sed -i "s~blah~$var~g" filedengan tanda kutip hanya di sekitar sedskrip yang sebenarnya .
tripleee
5

Ini adalah pertanyaan lama, tetapi tidak ada jawaban di sini yang membahas operasi selain s/from/to/secara mendetail.

Bentuk umum sedpernyataan adalah

*address* *action*

di mana alamat dapat berupa rentang regex atau rentang nomor baris (atau kosong, dalam hal ini tindakan diterapkan ke setiap baris masukan). Jadi contohnya

sed '1,4d' file

akan menghapus baris 1 hingga 4 ( alamatnya adalah kisaran nomor baris 1,4dan tindakannya adalah dperintah hapus); dan

sed '/ick/,$s/foo/bar/' file

akan mengganti kemunculan pertama foodengan barpada baris mana pun antara kecocokan pertama di regex ickdan akhir file ( alamatnya adalah rentang /ick/,$dan tindakannya adalah sperintah pengganti s/foo/bar/).

Dalam konteks ini, jika ickberasal dari variabel, Anda bisa melakukannya

sed "/$variable/,\$s/foo/bar/"

(perhatikan penggunaan tanda kutip ganda, bukan tanda kutip tunggal, sehingga shell dapat menginterpolasi variabel, dan kebutuhan untuk mengutip tanda dolar literal di dalam tanda kutip ganda) tetapi jika variabel tersebut berisi garis miring, Anda akan mendapatkan kesalahan sintaks. (Shell memperluas variabel, lalu meneruskan string yang dihasilkan ke sed; jadi sedhanya melihat teks literal - ia tidak memiliki konsep variabel shell.)

Obatnya adalah dengan menggunakan pembatas yang berbeda (di mana jelas Anda harus dapat menggunakan karakter yang tidak dapat terjadi dalam nilai variabel), tetapi tidak seperti dalam s%foo%bar%kasus ini, Anda juga memerlukan garis miring terbalik sebelum pembatas jika Anda ingin menggunakan karakter yang berbeda. pembatas dari default /:

sed "\\%$variable%,\$s/foo/bar/" file

(di dalam tanda kutip tunggal, satu garis miring terbalik sudah cukup); atau Anda dapat melepaskan diri dari setiap garis miring pada nilai secara terpisah. Sintaks khusus ini hanya Bash:

sed "/${variable//\//\\/}/,\$s/foo/bar/" file

atau jika Anda menggunakan shell yang berbeda, coba

escaped=$(echo "$variable" | sed 's%/%\\/%g')
sed "s/$escaped/,\$s/foo/bar/" file

Untuk kejelasan, jika $variableberisi string 1/2maka perintah di atas akan sama dengan

sed '\%1/2%,$s/foo/bar/' file

dalam kasus pertama, dan

sed '/1\/2/,$s/foo/bar/' file

di detik.

tripleee
sumber
4

Gunakan Perl, di mana variabel adalah warga kelas pertama, tidak hanya memperluas makro:

var=/Users/Documents/name/file perl -pe 's/\Q$ENV{var}/replace/g' $file
  • -p membaca masukan baris demi baris dan mencetak baris tersebut setelah diproses
  • \Qmengutip semua karakter meta dalam string berikut (tidak diperlukan untuk nilai yang disajikan di sini, tetapi diperlukan jika nilai berisi [atau beberapa nilai lain yang khusus untuk ekspresi reguler)
choroba
sumber
Satu-satunya jawaban yang umumnya berguna untuk lusinan pertanyaan "menggunakan variabel dalam ekspresi sed" di Stack Exchange. Segala sesuatu yang lain secara efektif "melarikan diri dari segala sesuatu yang khusus untuk sed", yang secara praktis tidak berguna mengingat variabilitas variabel dan sed.
Heath Raftery