Podziel ciąg na dwukropku w / bin / sh

9

Mój dashskrypt przyjmuje parametr w postaci hostname:portnp .:

myhost:1234

Natomiast port jest opcjonalny, tj .:

myhost

Muszę odczytać hosta i port w osobne zmienne. W pierwszym przypadku mogę wykonać:

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

Ale to nie działa w drugim przypadku, gdy port został pominięty; echo ${1##*:}po prostu zwraca nazwę hosta zamiast pustego ciągu.

W Bash mogłem:

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

Ale to nie działa dash.

Mogę podzielić na ciąg :w desce rozdzielczej, bez odwoływania się do zewnętrznych programów ( awk, tr, itd.)?

Martin Vegter
źródło
4
Pamiętaj, aby podzielić na ostatni dwukropek, jeśli chcesz obsługiwać IPv6, i nie
dziel
@ Ferrybig %%sprawia, że ​​jest chciwy (w przeciwieństwie do %), więc faktycznie robi to, przynajmniej częściowo; to nie zadziała ##.
jpaugh

Odpowiedzi:

18

Po prostu zrób:

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

Możesz zmienić wartość case $1na, case ${1##*[]]}aby uwzględnić wartości $1podobne [::1](adres IPv6 bez części portu ).

Aby podzielić, możesz użyć operatora split + glob (pozostaw rozszerzenie cudzysłowu bez cudzysłowu), ponieważ po to jest to:

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

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

(choć nie pozwoli to na nazwy hostów zawierające dwukropek (jak dla powyższego adresu IPv6).

Ten operator split + glob przeszkadza i powoduje tyle szkody przez resztę czasu, że wydawałoby się sprawiedliwe, że można go używać zawsze, gdy jest to potrzebne (choć zgodzę się, że korzystanie z niego jest bardzo kłopotliwe, zwłaszcza biorąc pod uwagę, że POSIX shnie ma wsparcie dla zakresu lokalnego, ani dla zmiennych ( $IFStutaj), ani dla opcji ( noglobtutaj) (choć ashi pochodne jak dashto tylko niektóre z tych, które wykonują (wraz z wdrożeń AT & T o ksh, zshi bash4.4 i wyżej)).

Pamiętaj, że IFS=: read A B <<< "$1"ma kilka własnych problemów:

  • zapomniałeś, -rco oznacza, że ​​ukośnik zostanie poddany specjalnej obróbce.
  • podzieliłby [::1]:443się na pusty łańcuch [i :1]:443zamiast [niego (do czego potrzebujesz IFS=: read -r A B rest_ignoredlub [::1]i 443(dla którego nie możesz użyć tego podejścia)
  • usuwa wszystko po pierwszym wystąpieniu znaku nowej linii, więc nie można go używać z dowolnymi ciągami znaków (chyba że użyjesz -d ''w zshlub bashdane nie zawierają znaków NUL, ale zwróć uwagę, że tutaj (lub heredoki) dodajemy dodatkowy znak nowej linii!)
  • in zsh(skąd pochodzi składnia), a bashtutaj łańcuchy są implementowane przy użyciu plików tymczasowych, więc jest ogólnie mniej wydajne niż używanie ${x#y}lub dzielenie operatorów + glob.
Stéphane Chazelas
źródło
7
W 2018 r., Jako rozwiązanie noworoczne, wszyscy powinniśmy przestać pisać skrypty, które zepsują się w IPv6.
Philippos,
@Filippos za późno o dwa tygodnie!
RonJohn
@RonJohn: Jakoś za późno o dwie dekady.
Philippos
6

Po prostu usuń :w osobnej instrukcji; również usuń $ host z wejścia, aby uzyskać port:

host=${1%:*}
port=${1#"$host"}
port=${port#:}
choroba
źródło
3

Kolejna myśl:

host=${1%:*}
port=${1##*:}
[ "$port" = "$1" ] && port=''
Glenn Jackman
źródło
1

Ciąg tutaj jest tylko składniowym skrótem do jednowierszowego dokumentu tutaj.

$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
chepner
źródło