Pisahkan string pada titik dua di / bin / sh

9

dashScript saya mengambil parameter dalam bentuk hostname:port, yaitu:

myhost:1234

Sedangkan port bersifat opsional, yaitu:

myhost

Saya perlu membaca host dan port menjadi variabel yang terpisah. Dalam kasus pertama, saya bisa melakukan:

HOST=${1%%:*}
PORT=${1##*:}

Tapi itu tidak berfungsi dalam kasus kedua, ketika port dihilangkan; echo ${1##*:}hanya mengembalikan nama host, alih-alih string kosong.

Di Bash, saya bisa melakukan:

IFS=: read A B <<< asdf:111

Tapi itu tidak berhasil dash.

Dapatkah saya membagi string di :dalam dasbor, tanpa melibatkan program eksternal ( awk, tr, dll)?

Martin Vegter
sumber
4
Pastikan untuk membelah pada titik dua terakhir jika Anda ingin mendukung IPv6, dan jangan membelah pada titik dua di dalam tanda kurung siku
Ferrybig
@Ferrybig %%menjadikannya serakah (berlawanan dengan %), jadi ia melakukan ini, setidaknya sebagian; itu tidak akan berhasil ##.
jpaugh

Jawaban:

18

Kerjakan saja:

case $1 in
  (*:*) host=${1%:*} port=${1##*:};;
  (*)   host=$1      port=$default_port;;
esac

Anda mungkin ingin mengubah case $1to case ${1##*[]]}ke akun untuk nilai $1like [::1](alamat IPv6 tanpa bagian port ).

Untuk memecah, Anda dapat menggunakan operator split + glob (biarkan ekspansi parameter tidak dikutip) karena memang untuk itulah:

set -o noglob # disable glob part
IFS=:         # split on colon
set -- $1     # split+glob

host=$1 port=${2:-$default_port}

(meskipun itu tidak akan mengizinkan nama host yang mengandung titik dua (seperti untuk alamat IPv6 di atas)).

Operator split + glob tersebut menghalangi dan menyebabkan begitu banyak kerusakan pada sisa waktu sehingga akan tampak adil jika digunakan kapan saja diperlukan (walaupun, saya setuju itu sangat rumit untuk digunakan terutama mengingat POSIX shtidak memiliki dukungan untuk ruang lingkup lokal, baik untuk variabel (di $IFSsini) maupun untuk opsi (di noglobsini) (meskipun ashdan turunannya seperti dashbeberapa yang melakukannya (bersama-sama dengan implementasi AT&T ksh, zshdan bash4.4 ke atas)).

Catatan yang IFS=: read A B <<< "$1"memiliki beberapa masalah sendiri:

  • Anda lupa -ryang artinya backslash akan menjalani beberapa pemrosesan khusus.
  • itu akan terpecah [::1]:443menjadi [dan :1]:443bukannya [ke string kosong (yang Anda butuhkan IFS=: read -r A B rest_ignoredatau [::1]dan 443(yang Anda tidak dapat menggunakan pendekatan itu)
  • itu strip semua masa lalu kejadian pertama dari karakter baris baru, sehingga tidak dapat digunakan dengan string sewenang-wenang (kecuali jika Anda menggunakan -d ''dalam zshatau bashdan data tidak mengandung karakter NUL, tapi kemudian diketahui bahwa herestrings (atau heredocs) jangan menambahkan karakter baris baru ekstra!)
  • di zsh( di mana sintaks berasal dari) dan bash, di sini string diimplementasikan menggunakan file sementara, sehingga umumnya kurang efisien daripada menggunakan ${x#y}atau membagi + operator glob.
Stéphane Chazelas
sumber
7
Pada 2018, sebagai resolusi Tahun Baru, kita semua harus berhenti menulis skrip yang akan terputus dengan IPv6.
Philippos
@ Pilipos terlambat dua minggu!
RonJohn
@ RonJohn: Terlambat dua dekade, entah bagaimana.
Philippos
6

Hapus saja :dalam pernyataan terpisah; juga, hapus $ host dari input untuk mendapatkan port:

host=${1%:*}
port=${1#"$host"}
port=${port#:}
choroba
sumber
3

Pikiran lain:

host=${1%:*}
port=${1##*:}
[ "$port" = "$1" ] && port=''
glenn jackman
sumber
1

String di sini hanyalah pintasan sintaksis untuk dokumen baris tunggal di sini.

$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
chepner
sumber