Ganti satu substring untuk string lain dalam skrip shell

823

Saya memiliki "Saya suka Suzi dan Menikah" dan saya ingin mengubah "Suzi" menjadi "Sara".

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
# do something...

Hasilnya harus seperti ini:

firstString="I love Sara and Marry"
Sengode
sumber
18
Saya akan mulai dengan membiarkan Suzi turun dengan lembut. :)
codesniffer
Itu Bukan kamu itu aku ! yang seharusnya menjadi string untuk berbicara dengan Suzi
GoodSp33d

Jawaban:

1360

Untuk mengganti kemunculan pertama pola dengan string yang diberikan, gunakan :${parameter/pattern/string}

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
echo "${firstString/Suzi/$secondString}"    
# prints 'I love Sara and Marry'

Untuk mengganti semua kejadian, gunakan :${parameter//pattern/string}

message='The secret code is 12345'
echo "${message//[0-9]/X}"           
# prints 'The secret code is XXXXX'

(Ini didokumentasikan dalam dalam Pedoman Bash Reference , §3.5.3 "Shell Parameter Ekspansi" .)

Perhatikan bahwa fitur ini tidak ditentukan oleh POSIX - ini adalah ekstensi Bash - jadi tidak semua shell Unix menerapkannya. Untuk dokumentasi POSIX yang relevan, lihat Spesifikasi Pangkalan Standar Teknis Grup Terbuka, Edisi 7 , volume Shell & Utilities , §2.6.2 "Ekspansi Parameter" .

ruakh
sumber
3
@ruakh bagaimana cara menulis pernyataan ini dengan atau kondisi. Hanya jika saya ingin mengganti Suzi atau Menikah dengan string baru.
Priyatham51
3
@ Priyatham51: Tidak ada fitur bawaan untuk itu. Cukup ganti satu, lalu yang lain.
ruakh
4
@ Bun: Tidak, karena \ndalam konteks itu akan mewakili dirinya sendiri, bukan baris baru. Saya tidak punya Bash berguna sekarang untuk menguji, tetapi Anda harus dapat menulis sesuatu seperti $STRING="${STRING/$'\n'/<br />}",. (Meskipun Anda mungkin ingin STRING//- ganti semua - bukan hanya STRING/.)
ruakh
25
Agar jelas, karena ini sedikit membingungkan saya, bagian pertama harus menjadi referensi variabel. Anda tidak bisa melakukannya echo ${my string foo/foo/bar}. Anda perluinput="my string foo"; echo ${input/foo/bar}
Henrik N
2
@mgutt: Jawaban ini terhubung ke dokumentasi yang relevan. Dokumentasi tidak sempurna - misalnya, alih-alih menyebutkan //secara langsung, ia menyebutkan apa yang terjadi "Jika pola dimulai dengan ' /'" (yang bahkan tidak akurat, karena sisa kalimat itu mengasumsikan bahwa tambahan /itu sebenarnya bukan bagian dari polanya) - tapi saya tidak tahu sumber yang lebih baik.
ruakh
208

Ini dapat dilakukan sepenuhnya dengan manipulasi string bash:

first="I love Suzy and Mary"
second="Sara"
first=${first/Suzy/$second}

Itu hanya akan menggantikan kejadian pertama; untuk mengganti semuanya, gandakan tebasan pertama:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"
Kevin
sumber
9
Apa gunanya memiliki jawaban yang menduplikasi jawaban yang diterima, alih-alih mengedit jawaban yang diterima dengan opsi penggantian global? Dan menambahkan bahwa regexps didukung, sambil melakukannya. Dan menjelaskan apa yang terjadi dengan "substitusi buruk". Anda memiliki cukup poin,
@Kevin
6
Tampaknya mereka berdua menjawab di menit yang sama: O
Jamie Hutber
76

Untuk Dash semua posting sebelumnya tidak berfungsi

shSolusi yang kompatibel dengan POSIX adalah:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")
Roman Kazanovskyi
sumber
1
Saya mendapatkan ini: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') Saya suka $ secondString dan Menikah
Qstnr_La
2
@Qstnr_La menggunakan tanda kutip ganda untuk substitusi variabel:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc
1
Plus 1 untuk menunjukkan cara output ke variabel juga. Terima kasih!
Chef Firaun
2
Saya memperbaiki tanda kutip tunggal dan juga menambahkan tanda kutip yang hilang di sekitar echoargumen. Itu menipu bekerja tanpa mengutip dengan string sederhana, tetapi dengan mudah istirahat pada string input nontrivial (spasi tidak teratur, karakter meta shell, dll).
tripleee
Dalam sh (AWS Codebuild / Ubuntu sh) saya menemukan bahwa saya perlu satu tebasan pada akhirnya, bukan ganda. Saya akan mengedit komentar karena komentar di atas juga menunjukkan garis miring.
Tim
67

coba ini:

 sed "s/Suzi/$secondString/g" <<<"$firstString"
Kent
sumber
19
Anda sebenarnya tidak membutuhkan Sed untuk ini; Bash mendukung penggantian semacam ini secara asli.
ruakh
12
Saya kira ini ditandai "bash" tetapi datang ke sini karena membutuhkan sesuatu yang sederhana untuk shell lain. Ini adalah alternatif ringkas yang bagus untuk apa yang wiki.ubuntu.com/... buat seperti yang saya butuhkan.
natevw
3
Ini berfungsi baik untuk ash / dash atau POSIX sh lainnya.
Yoshua Wuyts
2
Saya mendapatkan galat : -e ekspresi # 1, char 9: opsi tidak diketahui untuk `s
Nam G VU
1
Jawaban pertama yang bekerja dengan stdin, bagus untuk string pipelining.
Dinei
46

Lebih baik menggunakan bash daripada sedjika string memiliki karakter RegExp.

echo ${first_string/Suzi/$second_string}

Ini portabel untuk Windows dan bekerja dengan setidaknya setua Bash 3.1.

Untuk menunjukkan bahwa Anda tidak perlu terlalu khawatir tentang melarikan diri, mari kita ubah ini:

/home/name/foo/bar

Ke dalam ini:

~/foo/bar

Tetapi hanya jika /home/namedi awal. Kami tidak butuh sed!

Mengingat bahwa bash memberi kita variabel ajaib $PWDdan $HOME, kita dapat:

echo "${PWD/#$HOME/\~}"

EDIT: Terima kasih untuk Mark Haferkamp dalam komentar untuk catatan tentang mengutip / melarikan diri ~. *

Perhatikan bagaimana variabel $HOMEberisi garis miring tetapi ini tidak merusak apa pun.

Bacaan lebih lanjut: Panduan Bash-Scripting Lanjutan .
Jika menggunakan sedadalah suatu keharusan, pastikan untuk melepaskan setiap karakter .

Camilo Martin
sumber
Jawaban ini menghentikan saya dari menggunakan seddengan pwdperintah untuk menghindari mendefinisikan variabel baru setiap kali kebiasaan saya $PS1berjalan. Apakah Bash menyediakan cara yang lebih umum daripada variabel sihir untuk menggunakan output dari perintah untuk penggantian string? Adapun kode Anda, saya harus melarikan diri ~untuk menjaga Bash dari mengembangkannya menjadi $ HOME. Juga, apa yang dilakukan #perintah Anda?
Mark Haferkamp
1
@MarkHaferkamp Lihat ini dari tautan "bacaan lanjutan yang disarankan". Tentang "lolos dari ~": perhatikan bagaimana saya mengutip barang. Ingatlah untuk selalu mengutip barang! Dan ini tidak hanya berfungsi untuk variabel ajaib: variabel apa pun dapat diganti, mendapatkan panjang string, dan banyak lagi, dalam bash. Selamat mencoba $PS1puasa Anda: Anda mungkin juga tertarik $PROMPT_COMMANDjika Anda lebih nyaman menggunakan bahasa pemrograman lain dan ingin membuat kode prompt yang dikompilasi.
Camilo Martin
Tautan "bacaan lebih lanjut" menjelaskan "#". Pada Bash 4.3.30, echo "${PWD/#$HOME/~}"tidak menggantikan $HOMEdengan saya ~. Mengganti ~dengan \~atau '~'berfungsi. Semua ini bekerja pada Bash 4.2.53 pada distro lain. Bisakah Anda memperbarui posting Anda untuk mengutip atau menghindari ~kompatibilitas yang lebih baik? Apa yang saya maksud dengan pertanyaan "variabel ajaib" saya adalah: Dapatkah saya menggunakan substitusi variabel Bash pada, misalnya, keluaran unametanpa menyimpannya sebagai variabel terlebih dahulu? Sedangkan untuk pribadi saya $PROMPT_COMMAND, ini rumit .
Mark Haferkamp
@ MarkHaferkamp Wah, kau benar sekali, salahku. Akan memperbarui jawabannya sekarang.
Camilo Martin
1
@MarkHaferkamp Bash dan perangkapnya yang tidak jelas ...: P
Camilo Martin
19

Jika besok Anda memutuskan tidak mencintai Marry, ia juga bisa diganti:

today=$(</tmp/lovers.txt)
tomorrow="${today//Suzi/Sara}"
echo "${tomorrow//Marry/Jesica}" > /tmp/lovers.txt

Pasti ada 50 cara untuk meninggalkan kekasih Anda.

Josh Habdas
sumber
9

Karena saya tidak bisa menambahkan komentar. @ruaka Agar contohnya lebih mudah dibaca, tulis seperti ini

full_string="I love Suzy and Mary"
search_string="Suzy"
replace_string="Sara"
my_string=${full_string/$search_string/$replace_string}
or
my_string=${full_string/Suzy/Sarah}
Nate Revo
sumber
Sampai saya datang ke teladan Anda, saya telah memahami urutan sebaliknya. Ini membantu memperjelas apa yang terjadi
raghav710
5

Pure POSIX metode shell, yang tidak seperti Roman Kazanovskyi 's sedjawaban berbasis tidak memerlukan alat eksternal, hanya shell sendiri asli ekspansi parameter . Perhatikan bahwa nama file yang panjang diminimalkan sehingga kode lebih cocok pada satu baris:

f="I love Suzi and Marry"
s=Sara
t=Suzi
[ "${f%$t*}" != "$f" ] && f="${f%$t*}$s${f#*$t}"
echo "$f"

Keluaran:

I love Sara and Marry

Bagaimana itu bekerja:

  • Hapus Pola Sufiks Terkecil. "${f%$t*}"mengembalikan " I love" jika sufiks $t" Suzi*" ada di $f" ". I love Suzi and Marry

  • Tetapi jika t=Zelda, maka "${f%$t*}"tidak menghapus apa pun, dan mengembalikan seluruh string " I love Suzi and Marry".

  • Ini digunakan untuk menguji apakah $tdalam $fdengan [ "${f%$t*}" != "$f" ]yang akan mengevaluasi benar jika $fstring berisi " Suzi*" dan palsu jika tidak.

  • Jika pengujian mengembalikan true , buat string yang diinginkan menggunakan Hapus Pola Sufiks Terkecil ${f%$t*} " I love" dan Hapus Pola Awalan Terkecil ${f#*$t} " and Marry", dengan string ke - 2$s " Sara" di antaranya.

agc
sumber
5

Saya tahu ini sudah tua tetapi karena tidak ada yang menyebutkan tentang menggunakan awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'
Payam
sumber
1
Trik itu adalah cara untuk pergi jika apa yang Anda coba bagi tidak benar-benar dalam variabel tetapi dalam file teks. Terima kasih, @Payam!
Felipe SS Schneider
2
echo [string] | sed "s/[original]/[target]/g"
  • "s" berarti "pengganti"
  • "g" berarti "global, semua kejadian yang cocok"
Alberto Salvia Novella
sumber