División de cadena en dos puntos en / bin / sh

9

Mi dashscript toma un parámetro en forma de hostname:port, es decir:

myhost:1234

Mientras que el puerto es opcional, es decir:

myhost

Necesito leer el host y el puerto en variables separadas. En el primer caso, puedo hacer:

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

Pero eso no funciona en el segundo caso, cuando se omitió el puerto; echo ${1##*:}simplemente devuelve el nombre de host, en lugar de una cadena vacía.

En Bash, podría hacer:

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

Pero eso no funciona dash.

¿Puedo dividir la cadena en :en el tablero, sin invocar programas externos ( awk, tr, etc.)?

Martin Vegter
fuente
44
Asegúrese de dividir en el último colon si desea admitir IPv6, y no dividir en dos puntos entre corchetes
Ferrybig
@Ferrybig lo %%hace codicioso (en oposición a %), por lo que en realidad hace esto, al menos en parte; que no funcionaría con ##.
jpaugh

Respuestas:

18

Solo haz:

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

Es posible que desee cambiar case $1a case ${1##*[]]}para tener en cuenta valores $1similares [::1](una dirección IPv6 sin parte de puerto ).

Para dividir, puede usar el operador split + glob (deje una expansión de parámetro sin comillas) ya que para eso es:

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

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

(aunque eso no permitirá nombres de host que contengan dos puntos (como para la dirección IPv6 anterior)).

Ese operador split + glob se interpone en el camino y causa tanto daño el resto del tiempo que parecería justo que se use cuando sea necesario (aunque, estoy de acuerdo en que es muy engorroso de usar, especialmente teniendo en cuenta que POSIX shno tiene soporte para el alcance local, ni para las variables ( $IFSaquí) ni para las opciones ( noglobaquí) (aunque ashy los derivados como dashson algunos de los que sí lo hacen (junto con las implementaciones de AT&T ksh, zshy bash4.4 y superiores)).

Tenga en cuenta que IFS=: read A B <<< "$1"tiene algunos problemas propios:

  • olvidó lo -rque significa que la barra invertida se someterá a un procesamiento especial.
  • se dividiría [::1]:443en [y en :1]:443lugar de [y la cadena vacía (para la que necesitarías IFS=: read -r A B rest_ignoredo [::1]y 443(para la que no puedes usar ese enfoque)
  • despoja de todo más allá de la primera aparición de un carácter de nueva línea, por lo que no se puede utilizar con cadenas arbitrarias (a menos que utilice -d ''en zsho bashy los datos no contiene caracteres NUL, pero tenga en cuenta que herestrings (o heredocs) no añadir una personaje de nueva línea extra!)
  • en zsh(de donde proviene la sintaxis) y bash, aquí, las cadenas se implementan usando archivos temporales, por lo que generalmente es menos eficiente que usar ${x#y}operadores de división o glob +.
Stéphane Chazelas
fuente
77
En 2018, como resolución de Año Nuevo, todos deberíamos dejar de escribir guiones que rompan con IPv6.
Philippos
@Philippos demasiado tarde por dos semanas!
RonJohn
@RonJohn: Demasiado tarde por dos décadas.
Philippos el
6

Simplemente elimine el :en una declaración separada; Además, elimine $ host de la entrada para obtener el puerto:

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

Otro pensamiento:

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

Una cadena here es solo un atajo sintáctico para un documento here de una sola línea.

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