Dividi la stringa sui due punti in / bin / sh


9

Il mio dashscript accetta un parametro sotto forma di hostname:port, ovvero:

myhost:1234

Considerando che il porto è facoltativo, cioè:

myhost

Devo leggere l'host e la porta in variabili separate. Nel primo caso, posso fare:

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

Ma ciò non funziona nel secondo caso, quando la porta è stata omessa; echo ${1##*:}restituisce semplicemente il nome host, anziché una stringa vuota.

In Bash, potrei fare:

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

Ma questo non funziona dash .

Posso dividere stringa su :nel cruscotto, senza invocare programmi esterni ( awk, tr, ecc)?


4
Assicurati di dividere gli ultimi due punti se vuoi supportare IPv6 e non dividere i due punti tra parentesi quadre
Ferrybig

@Ferrybig lo %%rende avido (al contrario di %), quindi in realtà lo fa, almeno in parte; non funzionerebbe con ##.
jpaugh

Risposte:


18

Basta fare:

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

È possibile che si desideri modificare case $1to case ${1##*[]]}per tenere conto dei valori di $1like [::1](un indirizzo IPv6 senza parte della porta ).

Per dividere, puoi usare l' operatore split + glob (non quotare l'espansione di un parametro) poiché dopo tutto è quello che serve:

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

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

(sebbene ciò non consentirà nomi host che contengono due punti (come per quell'indirizzo IPv6 sopra)).

Quell'operatore split + glob si mette in mezzo e provoca così tanto danno per il resto del tempo che sembrerebbe giusto che venga usato ogni volta che è necessario (anche se, concordo che è molto ingombrante da usare soprattutto considerando che POSIX shnon ha supporto per ambito locale, né per le variabili ( $IFSqui) né per le opzioni ( noglobqui) (sebbene ashe derivati ​​come dashalcuni di quelli che lo fanno (insieme alle implementazioni AT&T di ksh, zshe bash4.4 e successive)).

Nota che IFS=: read A B <<< "$1"ha alcuni problemi propri:

  • hai dimenticato il -rche significa che la barra rovesciata subirà qualche elaborazione speciale.
  • si dividerebbe [::1]:443in [e :1]:443invece di [e la stringa vuota (per la quale avresti bisogno IFS=: read -r A B rest_ignoredoe [::1]e443 (per la quale non puoi usare quell'approccio)
  • spoglia tutto oltre la prima occorrenza di un carattere di nuova riga, quindi non può essere utilizzato con stringhe arbitrarie (a meno che non si utilizzi -d ''in zsho bashe i dati non contengano caratteri NUL, ma poi si noti che le sue estromissioni (o heredocs) aggiungono un personaggio extra newline!)
  • in zsh(da dove proviene la sintassi) e bash, qui le stringhe sono implementate usando file temporanei, quindi è generalmente meno efficiente rispetto all'utilizzo ${x#y}o alla divisione + operatori glob.

7
Nel 2018, come risoluzione per il nuovo anno, dovremmo smettere tutti di scrivere script che si romperanno con IPv6.
Philippos,

@Philippos troppo tardi di due settimane!
RonJohn,

@RonJohn: troppo tardi di due decenni, in qualche modo.
Philippos,

6

Basta rimuovere il :in un'istruzione separata; inoltre, rimuovere $ host dall'input per ottenere la porta:

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

3

Un altro pensiero:

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

1

Una stringa qui è solo una scorciatoia sintattica per un documento qui a riga singola.

$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.