Сумісна відповідь
Існує маса різних способів зробити це баш.
Однак важливо спершу зазначити, що bash
є багато особливих особливостей (так званих башизмів ), які не працюватимуть ні в якому іншомуоболонки.
Зокрема, масиви , асоціативні масиви та заміна шаблонів , які використовуються в рішеннях на цій посаді, а також інші в потоці, є башизмами і можуть не працювати під іншими оболонками , якими користуються багато людей.
Наприклад: у моєму Debian GNU / Linux є стандартна оболонка під назвоютире; Я знаю багатьох людей, які люблять використовувати іншу оболонку під назвоюкш; а також є спеціальний інструмент, який називаєтьсяbusbox з власним перекладачем оболонок (зола).
Запитаний рядок
Рядок, який слід розділити у вищезазначеному питанні, є:
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
або хочете написати щось, що може бути використано у багатьох різних оболонках, ви часто не можете використовувати башизми - і це включає масиви, які ми використовували у вищезазначених рішеннях.
Однак нам не потрібно використовувати масиви для переходу на "елементи" рядка. Існує синтаксис, який використовується в багатьох оболонках для видалення підрядків рядка з першого або останнього появи шаблону. Зауважте, що *
це підстановка, яка означає нуль або більше символів:
(Відсутність такого підходу в будь-якому досі розміщеному рішенні є основною причиною написання цієї відповіді;)
${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
),тире, кш, і busbox'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>]
Веселіться!