Разделить строку на двоеточие в / bin / sh

9

Мой dashскрипт принимает параметр в виде hostname:port, то есть:

myhost:1234

Принимая во внимание, что порт является необязательным, то есть:

myhost

Мне нужно прочитать хост и порт в отдельных переменных. В первом случае я могу сделать:

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

Но это не работает во втором случае, когда порт был опущен; echo ${1##*:}просто возвращает имя хоста вместо пустой строки.

В Bash я мог бы сделать:

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

Но это не работает в dash.

Можно ли разбить строку на :тире, не вызывая внешние программы ( awk, trи т. Д.)?

Мартин Вегтер
источник
4
Обязательно
разбивайте
@Ferrybig %%делает его жадным (в отличие от %), поэтому он действительно делает это, по крайней мере, частично; это не будет работать с ##.
18:30

Ответы:

18

Просто делать:

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

Возможно, вы захотите изменить на, case $1чтобы case ${1##*[]]}учесть значения $1like [::1](адрес IPv6 без части порта ).

Чтобы разделить, вы можете использовать оператор split + glob (оставить расширение параметра без кавычек), так как для этого он и нужен:

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

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

(хотя это не позволит использовать имена хостов, содержащие двоеточие (как для этого адреса IPv6 выше)).

Этот оператор split + glob мешает и причиняет столько вреда в остальное время, что было бы совершенно справедливо использовать его всякий раз, когда это необходимо (хотя, я согласен, его очень громоздко использовать, особенно учитывая, что POSIX shне имеет поддержка локальной области видимости, ни для переменных ( $IFSздесь), ни для опций ( noglobздесь) (хотя ashи dashнекоторые производные, такие как (вместе с реализациями AT & T ksh, zshи bash4.4 и выше)).

Обратите внимание, что IFS=: read A B <<< "$1"есть несколько собственных проблем:

  • Вы забыли, -rчто означает, что обратная косая черта подвергнется специальной обработке.
  • было бы разделить [::1]:443на [и :1]:443вместо того , чтобы [и пустую строку (для которой вам нужно IFS=: read -r A B rest_ignoredили [::1]и 443(для которых вы не можете использовать этот подход)
  • он удаляет все после первого вхождения символа новой строки, поэтому его нельзя использовать с произвольными строками (если только вы не используете -d ''в zshили bashданные не содержат NUL-символов, но затем обратите внимание, что здесь-строки (или heredocs) действительно добавляют дополнительный символ новой строки!)
  • в zsh(откуда исходит синтаксис) и bashздесь строки реализуются с использованием временных файлов, поэтому обычно они менее эффективны, чем использование ${x#y}операторов split или glob.
Стефан Шазелас
источник
7
В 2018 году, как новогоднее решение, мы все должны прекратить писать сценарии, которые порвут с IPv6.
Филиппос
@ Филиппос опоздал на две недели!
RonJohn
@RonJohn: Как-то слишком поздно на два десятилетия.
Филиппос
6

Просто удалите :в отдельном заявлении; также удалите $ host из ввода, чтобы получить порт:

host=${1%:*}
port=${1#"$host"}
port=${port#:}
choroba
источник
3

Еще одна мысль:

host=${1%:*}
port=${1##*:}
[ "$port" = "$1" ] && port=''
Гленн Джекман
источник
1

Строка here - это всего лишь синтаксический ярлык для однострочного документа здесь.

$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
chepner
источник