Совместимый ответ
Есть много разных способов сделать это в удар,
Тем не менее, важно сначала отметить, что bash
имеет много специальных функций (так называемых bashisms ), которые не будут работать ни в одном другомракушка,
В частности, массивы , ассоциативные массивы и подстановка шаблонов , которые используются в решениях в этом посте, а также в других цепочках , являются ошибками и могут не работать под другими оболочками, которые используют многие люди.
Например: на моем Debian GNU / Linux есть стандартная оболочка под названиемтире; Я знаю многих людей, которые любят использовать другую оболочку под названиемКШ; и есть также специальный инструмент под названиемBusyBox с его собственным интерпретатором оболочки (ясень).
Запрашиваемая строка
Строка, которая будет разбита в приведенном выше вопросе:
IN="bla@some.com;john@home.com"
Я буду использовать модифицированную версию этой строки, чтобы убедиться, что мое решение устойчиво к строкам, содержащим пробелы, которые могут нарушить другие решения:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Разделить строку на основе разделителя в удар (версия> = 4.2)
В чистом виде bash
мы можем создать массив с элементами, разделенными временным значением для IFS ( разделитель входного поля ). IFS, помимо прочего, сообщает, bash
какой символ (символы) он должен рассматривать как разделитель между элементами при определении массива:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
В более новых версиях bash
, предварив команду с определением МФСА изменяет IFS для этой команды только и сбрасывает его в предыдущее значение сразу же после этого. Это означает, что мы можем сделать выше всего одну строку:
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
Мы можем видеть, что строка IN
была сохранена в массив с именем fields
, разделенный на точки с запятой:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
(Мы также можем отобразить содержимое этих переменных, используя declare -p
:)
declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
Обратите внимание, что read
это самый быстрый способ сделать разделение, потому что нет никаких вызванных вилок или внешних ресурсов.
Как только массив определен, вы можете использовать простой цикл для обработки каждого поля (или, вернее, каждого элемента в массиве, который вы сейчас определили):
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Или вы можете удалить каждое поле из массива после обработки, используя подход смещения , который мне нравится:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
И если вам нужна простая распечатка массива, вам даже не нужно зацикливаться на нем:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Обновление: недавно удар > = 4.4
В новых версиях bash
вы также можете играть с командой mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Этот синтаксис сохраняет специальные символы, новые строки и пустые поля!
Если вы не хотите включать пустые поля, вы можете сделать следующее:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
С помощью mapfile
вы также можете пропустить объявление массива и неявно «зацикливаться» на элементах с разделителями, вызывая функцию для каждого:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(Примечание: \0
конец строки формата бесполезен, если вам не нужны пустые поля в конце строки или они отсутствуют.)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Или вы можете использовать <<<
, и в теле функции включить некоторую обработку для удаления новой строки:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
# Renders the same output:
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Разделить строку на основе разделителя в ракушка
Если вы не можете использовать bash
или если вы хотите написать что-то, что можно использовать во многих различных оболочках, вы часто не можете использовать bashisms - и это включает в себя массивы, которые мы использовали в решениях выше.
Однако нам не нужно использовать массивы для циклического перебора «элементов» строки. Существует синтаксис, используемый во многих оболочках для удаления подстрок строки из первого или последнего вхождения шаблона. Обратите внимание, что *
подстановочный знак обозначает ноль или более символов:
(Отсутствие такого подхода в любом опубликованном решении является основной причиной, по которой я пишу этот ответ;)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
Как объяснил Score_Under :
#
и %
удалите максимально короткую подходящую подстроку из начала и конца строки соответственно, и
##
и %%
удалите максимально длинную подходящую подстроку.
Используя приведенный выше синтаксис, мы можем создать подход, в котором мы извлекаем «элементы» подстроки из строки, удаляя подстроки до или после разделителя.
Кодовый блок ниже хорошо работает в удар(включая Mac OS bash
),тире, КШ, а также BusyBox«s ясень:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Радоваться, веселиться!