Как разделить строку на разделитель в Bash? - PullRequest
1755 голосов
/ 28 мая 2009

Эта строка хранится в переменной:

IN="bla@some.com;john@home.com"

Теперь я хотел бы разделить строки на ; разделитель так, чтобы у меня было:

ADDR1="bla@some.com"
ADDR2="john@home.com"

Мне не обязательно нужны переменные ADDR1 и ADDR2. Если они являются элементами массива, это даже лучше.


После предложений из приведенных ниже ответов я получил следующее:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

Выход:

> [bla@some.com]
> [john@home.com]

Было решение, включающее установку Internal_field_separator (IFS) на ;. Я не уверен, что случилось с этим ответом. Как восстановить IFS назад по умолчанию?

RE: IFS решение, я попробовал это, и оно работает, я сохраняю старый IFS и затем восстанавливаю его:

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

Кстати, когда я пытался

mails2=($IN)

Я получил только первую строку при печати в цикле, без скобок вокруг $IN это работает.

Ответы [ 34 ]

30 голосов
/ 05 июля 2011

Другой взгляд на Ответ Даррона , вот как я это делаю:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
26 голосов
/ 26 июня 2014

В Bash, пуленепробиваемый способ, который будет работать, даже если ваша переменная содержит символы новой строки:

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

Посмотрите:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

Хитрость для этого в том, чтобы использовать параметр -d read (разделитель) с пустым разделителем, так что read вынужден читать все, что ему подается. И мы передаем read точно с содержимым переменной in, без завершающей строки, благодаря printf. Обратите внимание, что мы также помещаем разделитель в printf, чтобы строка, переданная в read, имела конечный разделитель. Без него read обрезает потенциальные конечные пустые поля:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

конечное пустое поле сохраняется.


Обновление для Bash≥4,4

Начиная с Bash 4.4, встроенный mapfile (он же readarray) поддерживает опцию -d для указания разделителя. Отсюда и другой канонический путь:

mapfile -d ';' -t array < <(printf '%s;' "$in")
21 голосов
/ 14 сентября 2010

Как насчет этого одного лайнера, если вы не используете массивы:

IFS=';' read ADDR1 ADDR2 <<<$IN
19 голосов
/ 11 сентября 2015

Вот чистый 3-х вкладыш:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

где IFS слова-разделители на основе разделителя, а () используется для создания массива . Затем [@] используется для возврата каждого элемента в виде отдельного слова.

Если после этого у вас есть какой-либо код, вам также необходимо восстановить $IFS, например, unset IFS.

16 голосов
/ 01 августа 2016

Без настройки IFS

Если у вас есть только двоеточие, вы можете сделать это:

a="foo:bar"
b=${a%:*}
c=${a##*:}

вы получите:

b = foo
c = bar
9 голосов
/ 24 мая 2017

Следующая функция Bash / zsh разбивает свой первый аргумент на разделитель, заданный вторым аргументом:

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

Например, команда

$ split 'a;b;c' ';'

выходы

a
b
c

Этот вывод может, например, передаваться другим командам. Пример:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

По сравнению с другими представленными решениями это имеет следующие преимущества:

  • IFS не переопределяется: из-за динамического выделения четных локальных переменных переопределение IFS над циклом приводит к утечке нового значения в вызовы функций, выполняемые из цикла.

  • Массивы не используются: для чтения строки в массив с использованием read требуется флаг -a в Bash и -A в zsh.

При желании функцию можно поместить в скрипт следующим образом:

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"
7 голосов
/ 16 сентября 2015

Существует простой и умный способ, подобный этому:

echo "add:sfff" | xargs -d: -i  echo {}

Но вы должны использовать gnu xargs, BSD xargs не может поддерживать -d delim. Если вы используете Apple Mac, как я. Вы можете установить GNU XARGS:

brew install findutils

тогда

echo "add:sfff" | gxargs -d: -i  echo {}
6 голосов
/ 20 января 2018

Вы можете применить awk ко многим ситуациям

echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

также вы можете использовать это

echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"
5 голосов
/ 25 сентября 2011

Это самый простой способ сделать это.

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
4 голосов
/ 22 октября 2012

Здесь есть несколько классных ответов (errator esp.), Но для чего-то аналогичного разделению на других языках - что я и имел в виду в первоначальном вопросе - я остановился на этом:

IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";

Теперь ${a[0]}, ${a[1]} и т. Д., Как и следовало ожидать. Используйте ${#a[*]} для количества терминов. Или, конечно, повторить:

for i in ${a[*]}; do echo $i; done

ВАЖНОЕ ПРИМЕЧАНИЕ:

Это работает в тех случаях, когда нет места для беспокойства, что решило мою проблему, но не может решить вашу. В этом случае воспользуйтесь решением $IFS.

...