Розділити рядок на двокрапку в / бін / ш


9

Мій dashсценарій приймає параметр у вигляді hostname:port, тобто:

myhost:1234

Тоді як порт необов’язковий, тобто:

myhost

Мені потрібно прочитати хост і порт в окремі змінні. У першому випадку я можу:

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

Але це не працює у другому випадку, коли порт був пропущений; echo ${1##*:}просто повертає ім'я хоста замість порожнього рядка.

У Bash я міг зробити:

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

Але це не працює dash.

Можу чи я розділити рядок на :в тирі, без залучення зовнішніх програм ( awk, tr, і т.д.)?


4
Не забудьте розділити на останню товсту кишку, якщо хочете підтримувати IPv6, а не
розбивайтесь на колони

@Ferrybig %%робить це жадібним (на відміну від %), тому він насправді робить це, принаймні частково; це б не працювало ##.
jpaugh

Відповіді:


18

Просто зробіть:

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

Можливо, ви захочете змінити case $1на, case ${1##*[]]}щоб урахувати значення $1типу [::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 вище)).

Цей оператор спліт-глобус перешкоджає та заподіює стільки шкоди в інший час, що, здавалося б, справедливо, що він буде використовуватися, коли це потрібно (хоча, я згоден, це дуже громіздко використовувати, особливо враховуючи, що 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, але потім зауважте, що єрести (або гередоки) додають додатковий символ нового рядка!)
  • in zsh(звідки походить синтаксис), і bashтут рядки реалізуються за допомогою тимчасових файлів, тому це, як правило, менш ефективно, ніж використання ${x#y}операторів або split + glob.

7
У 2018 році, як новорічна резолюція, ми повинні всі припинити писати сценарії, які будуть порушені з IPv6.
Філіппос

@Philippos занадто пізно на два тижні!
РонДжон

@RonJohn: Занадто пізно на два десятиліття.
Філіпос

6

Просто видаліть :окремий оператор; також видаліть $ хост із входу, щоб отримати порт:

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


1

Рядок тут - це лише синтаксичний ярлик для однорядного документа тут.

$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.