Hapus awalan / sufiks tetap dari string di Bash

485

Dalam bashskrip saya, saya memiliki string dan awalan / sufiksnya. Saya perlu menghapus awalan / akhiran dari string asli.

Misalnya, katakan saya memiliki nilai berikut:

string="hello-world"
prefix="hell"
suffix="ld"

Bagaimana saya mendapatkan hasil berikut?

result="o-wor"
Dušan Rychnovský
sumber
5
Lihatlah Panduan Bash-Scripting Lanjutan
tarrsalah
14
Berhati-hatilah saat menautkan yang disebut Panduan Bash Scripting Lanjutan; itu berisi campuran nasihat yang baik dan mengerikan.
tripleee

Jawaban:

719
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor
Adrian Frühwirth
sumber
40
Ada juga ## dan %%, yang menghapus sebanyak mungkin jika $ awalan atau $ akhiran mengandung wildcard.
Poin
28
Apakah ada cara untuk menggabungkan keduanya dalam satu baris? Saya mencoba ${${string#prefix}%suffix}tetapi tidak berhasil.
static_rtti
28
@static_rtti Tidak, sayangnya Anda tidak dapat membuat substitusi parameter seperti ini. Aku tahu, ini memalukan.
Adrian Frühwirth
87
@ AdrianFrühwirth: seluruh bahasa itu memalukan, tapi ini sangat berguna :)
static_rtti
8
Nvm, "substitusi bash" di Google menemukan apa yang saya inginkan.
Tyler
89

Menggunakan sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

Dalam perintah sed, ^karakter tersebut cocok dengan teks yang dimulai dengan $prefix, dan $teks yang cocok dengan yang berakhir dengan $suffix.

Adrian Frühwirth membuat beberapa poin bagus dalam komentar di bawah, tetapi seduntuk tujuan ini bisa sangat berguna. Fakta bahwa isi $ awalan dan $ akhiran ditafsirkan oleh sed bisa baik atau buruk - selama Anda memperhatikan, Anda harus baik-baik saja. Keindahannya adalah, Anda dapat melakukan sesuatu seperti ini:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

yang mungkin apa yang Anda inginkan, dan lebih bagus dan lebih kuat daripada substitusi variabel bash. Jika Anda ingat bahwa dengan kekuatan besar datang tanggung jawab besar (seperti kata Spiderman), Anda harus baik-baik saja.

Pengantar sed yang cepat dapat ditemukan di http://evc-cit.info/cit052/sed_tutorial.html

Catatan tentang shell dan penggunaan string:

Untuk contoh khusus yang diberikan, berikut ini akan berfungsi juga:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

... tetapi hanya karena:

  1. gema tidak peduli berapa banyak string dalam daftar argumennya, dan
  2. Tidak ada spasi dalam $ awalan dan $ akhiran

Biasanya merupakan praktik yang baik untuk mengutip string pada baris perintah karena meskipun berisi spasi, akan disajikan ke perintah sebagai argumen tunggal. Kami mengutip $ awalan dan $ akhiran untuk alasan yang sama: setiap perintah edit ke sed akan diteruskan sebagai satu string. Kami menggunakan tanda kutip ganda karena mereka memungkinkan interpolasi variabel; seandainya kita menggunakan tanda kutip tunggal perintah sed akan mendapatkan literal $prefixdan $suffixyang jelas bukan yang kita inginkan.

Perhatikan juga, saya menggunakan tanda kutip tunggal ketika mengatur variabel prefixdan suffix. Kami tentu saja tidak ingin interpretasi apa pun dalam string, jadi kami mengutipnya sehingga tidak ada interpolasi yang terjadi. Sekali lagi, ini mungkin tidak perlu dalam contoh ini tetapi itu kebiasaan yang sangat baik untuk masuk.

Chris Kolodin
sumber
8
Sayangnya, ini saran yang buruk karena beberapa alasan: 1) Tidak dikutip, $stringtunduk pada pemisahan kata dan penggumpalan. 2) $prefixdan $suffixdapat berisi ekspresi yang sedakan menafsirkan, misalnya ekspresi reguler atau karakter yang digunakan sebagai pembatas yang akan memecah seluruh perintah. 3) Memanggil seddua kali tidak perlu (Anda dapat melakukannya -e 's///' -e '///') dan pipa juga bisa dihindari. Sebagai contoh, pertimbangkan string='./ *'dan / atau prefix='./'dan lihat itu rusak parah karena 1)dan 2).
Adrian Frühwirth
Catatan menyenangkan: sed dapat mengambil hampir semua hal sebagai pembatas. Dalam kasus saya, karena saya parsing direktori-awalan keluar dari jalan, saya tidak bisa menggunakan /, jadi saya menggunakan sed "s#^$prefix##, sebagai gantinya. (Kerentanan: nama file tidak dapat mengandung #. Karena saya mengontrol file, kami aman, di sana.)
Olie
@Olie Nama file dapat berisi karakter apa pun kecuali karakter slash dan null jadi kecuali Anda memegang kendali Anda tidak dapat menganggap nama file untuk tidak mengandung karakter tertentu.
Adrian Frühwirth
Ya, tidak tahu apa yang saya pikirkan di sana. iOS mungkin? Tidak tahu Nama file pasti dapat berisi "#". Tidak tahu mengapa saya mengatakan itu. :)
Olie
@ Olie: Ketika saya memahami komentar asli Anda, Anda mengatakan bahwa batasan pilihan Anda untuk digunakan #sebagai pembatas sed berarti Anda tidak dapat menangani file yang mengandung karakter itu.
P Ayah
17

Apakah Anda tahu panjang awalan dan akhiran Anda? Dalam kasus Anda:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

Atau lebih umum:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

Tetapi solusi dari Adrian Frühwirth sangat keren! Saya tidak tahu tentang itu!

tommy.carstensen
sumber
14

Saya menggunakan grep untuk menghapus awalan dari jalur (yang tidak ditangani dengan baik oleh sed):

echo "$input" | grep -oP "^$prefix\K.*"

\K menghapus dari kecocokan semua karakter sebelum itu.

Vladimir Petrakovich
sumber
grep -Padalah ekstensi yang tidak standar. Lebih banyak kekuatan untuk Anda jika didukung pada platform Anda, tetapi ini adalah saran yang meragukan jika kode Anda perlu cukup portabel.
tripleee
@ Tripleee Memang. Tapi saya pikir sistem dengan GNU Bash yang diinstal juga memiliki grep yang mendukung PCRE.
Vladimir Petrakovich
1
Tidak, MacOS misalnya memiliki Bash di luar kotak tetapi bukan GNU grep. Versi sebelumnya sebenarnya memiliki -Popsi dari BSD greptetapi mereka menghapusnya.
tripleee
9
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

Catatan:

# $ awalan: menambahkan # memastikan bahwa "neraka" substring dihapus hanya jika ditemukan di awal. % $ suffix: menambahkan% memastikan bahwa substring "ld" dihapus hanya jika ditemukan pada akhirnya.

Tanpa ini, substring "neraka" dan "ld" akan dihapus di mana-mana, bahkan ditemukan di tengah.

Vijay Vat
sumber
Terima kasih untuk Notes! qq: dalam contoh kode Anda, Anda juga memiliki garis miring /tepat setelah string, untuk apa itu?
DiegoSalazar
1
/ memisahkan string saat ini dan sub-string. Sub-string di sini adalah akhiran dalam pertanyaan yang diposting.
Vijay Vat
7

Menggunakan =~operator :

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ [[ "$string" =~ ^$prefix(.*)$suffix$ ]] && echo "${BASH_REMATCH[1]}"
o-wor
Martin - マ ー チ ン
sumber
6

Solusi kecil dan universal:

expr "$string" : "$prefix\(.*\)$suffix"
Tosi Do
sumber
1
Jika Anda menggunakan Bash, Anda mungkin tidak boleh menggunakan exprsama sekali. Itu adalah semacam utilitas wastafel dapur yang mudah digunakan pada zaman cangkang Bourne asli, tetapi sekarang sudah melewati tanggal terbaiknya.
tripleee
5

Menggunakan @Adrian Frühwirth jawaban:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

gunakan seperti ini

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello
math2001
sumber
0

Saya akan menggunakan kelompok tangkap di regex:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ set +H # Disables history substitution, can be omitted in scripts.
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/" <<< $string
o-wor
$ string1=$string$string
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/g" <<< $string1
o-woro-wor

((?:(?!(${suffix})).)*) memastikan bahwa konten ${suffix} akan dikecualikan dari grup tangkap. Dalam hal contoh, ini setara dengan string [^A-Z]*. Kalau tidak, Anda akan mendapatkan:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
Bayou
sumber