Fractionner la chaîne sur deux points dans / bin / sh

9

Mon dashscript prend un paramètre sous la forme de hostname:port:

myhost:1234

Alors que le port est facultatif, c'est-à-dire:

myhost

J'ai besoin de lire l'hôte et le port dans des variables distinctes. Dans le premier cas, je peux faire:

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

Mais cela ne fonctionne pas dans le deuxième cas, lorsque le port a été omis; echo ${1##*:}renvoie simplement le nom d'hôte, au lieu d'une chaîne vide.

Dans Bash, je pourrais faire:

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

Mais cela ne fonctionne pas dash.

Puis - je diviser la chaîne sur :au tableau de bord, sans faire appel à des programmes externes ( awk, tr, etc.)?

Martin Vegter
la source
4
Assurez-vous de diviser sur le dernier deux-points si vous souhaitez prendre en charge IPv6, et ne
divisez
@Ferrybig le %%rend gourmand (par opposition à %), il le fait donc, au moins en partie; cela ne fonctionnerait pas avec ##.
jpaugh

Réponses:

18

Faites juste:

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

Vous souhaiterez peut-être modifier le case $1pour case ${1##*[]]}tenir compte des valeurs de $1similaires [::1](une adresse IPv6 sans partie de port ).

Pour diviser, vous pouvez utiliser l' opérateur split + glob (laisser une extension de paramètre sans guillemets) car c'est à cela qu'il sert après tout:

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

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

(bien que cela n'autorise pas les noms d'hôtes contenant deux points (comme pour l'adresse IPv6 ci-dessus)).

Cet opérateur split + glob se met en travers du chemin et cause tellement de dommages le reste du temps qu'il semblerait juste qu'il soit utilisé chaque fois qu'il est nécessaire (cependant, je conviendrai que c'est très lourd à utiliser, d'autant plus que POSIX shn'a pas prise en charge de la portée locale, ni pour les variables ( $IFSici) ni pour les options ( noglobici) (bien ashque des dérivés comme dashceux qui le font (avec les implémentations AT&T de ksh, zshet bash4.4 et ci-dessus)).

Notez que cela IFS=: read A B <<< "$1"a quelques problèmes qui lui sont propres:

  • vous avez oublié ce -rqui signifie que la barre oblique inverse subira un traitement spécial.
  • il serait divisé [::1]:443en [et :1]:443au lieu de [et la chaîne vide (pour laquelle vous auriez besoin IFS=: read -r A B rest_ignoredou [::1]et 443(pour lequel vous ne pouvez pas utiliser cette approche)
  • il supprime tout au-delà de la première occurrence d'un caractère de nouvelle ligne, il ne peut donc pas être utilisé avec des chaînes arbitraires (sauf si vous utilisez -d ''dans zshou bashet que les données ne contiennent pas de caractères NUL, mais notez ensuite que ses chaînes (ou heredocs) ajoutent un caractère de nouvelle ligne supplémentaire!)
  • dans zsh(d'où vient la syntaxe) et bash, ici, les chaînes sont implémentées à l'aide de fichiers temporaires, donc c'est généralement moins efficace que d'utiliser ${x#y}ou de diviser + des opérateurs glob.
Stéphane Chazelas
la source
7
En 2018, en tant que résolution du Nouvel An, nous devrions tous arrêter d'écrire des scripts qui rompront avec IPv6.
Philippos
@Philippos trop tard de deux semaines!
RonJohn
@ RonJohn: Trop tard de deux décennies, en quelque sorte.
Philippos
6

Supprimez simplement le :dans une déclaration distincte; supprimez également $ host de l'entrée pour obtenir le port:

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

Une autre pensée:

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

Une chaîne ici n'est qu'un raccourci syntaxique pour un document ici d'une seule ligne.

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