Как разделить строку на разделитель в 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 ]

1084 голосов
/ 28 мая 2009

Можно установить переменную внутреннего разделителя полей (IFS), а затем разрешить ее анализ в массив. Когда это происходит в команде, тогда присвоение IFS происходит только в среде этой отдельной команды (read). Затем он анализирует входные данные в соответствии со значением переменной IFS в массив, который мы затем можем перебрать.

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

Он проанализирует одну строку элементов, разделенных ;, и поместит ее в массив. Материал для обработки всего $IN, каждый раз одна строка ввода разделяется ;:

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"
855 голосов
/ 10 марта 2011

Взято из Разделенный массив сценариев Bash :

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

Пояснение:

Эта конструкция заменяет все вхождения ';' (начальное значение // означает глобальную замену) в строке IN на ' ' (один пробел), а затем интерпретирует строку с разделителями-пробелами как массив (это что делают окружающие скобки).

Синтаксис, используемый внутри фигурных скобок для замены каждого символа ';' на символ ' ', называется Расширение параметра .

Есть несколько распространенных ошибок:

  1. Если в исходной строке есть пробелы, вам нужно будет использовать IFS :
    • IFS=':'; arrIN=($IN); unset IFS;
  2. Если в исходной строке есть пробелы и , то разделителем является новая строка, вы можете установить IFS с помощью:
    • IFS=$'\n'; arrIN=($IN); unset IFS;
222 голосов
/ 28 мая 2009

Если вы не возражаете немедленно обработать их, мне нравится делать это:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

Вы можете использовать этот тип цикла для инициализации массива, но, вероятно, есть более простой способ сделать это. Надеюсь, это поможет.

161 голосов
/ 13 апреля 2013

Совместимый ответ

К этому такому вопросу в уже есть много разных способов сделать это. Но в bash есть много специальных функций, так называемых bashism , которые работают хорошо, но не будут работать в любой другой .

В частности, массивы , ассоциативный массив и замена шаблона являются чистыми bashisms и могут не работать при других оболочки .

На моем Debian GNU / Linux есть стандартная оболочка с именем , но я знаю многих людей, которые любят использовать .

Наконец, в очень маленькой ситуации есть специальный инструмент под названием с собственным интерпретатором оболочки ().

Запрошенная строка

Пример строки в SO вопросе:

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

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

 IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

Разделить строку на основе разделителя в (версия> = 4.2)

При pure bash мы можем использовать массивы и IFS :

var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS

IFS=\; read -a fields <<<"$IN"

Использование этого синтаксиса в недавнем bash не изменяет $IFS для текущего сеанса, но только для текущей команды:

set | grep ^IFS=
IFS=$' \t\n'

Теперь строка var разбивается и сохраняется в массив (с именем fields):

set | grep ^fields=\\\|^var=
fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
var='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 - это самый быстрый способ сделать разделение, потому что нет вилок и не называются внешние ресурсы.

Оттуда вы можете использовать синтаксис, который вы уже знаете, для обработки каждого поля:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]

или отбросьте каждое поле после обработки (мне нравится этот смещенный подход):

while [ "$fields" ] ;do
    echo "> [$fields]"
    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

Вы можете играть с mapfile:

mapfile -td \; fields < <(printf "%s\0" "$IN")

Этот синтаксис сохраняет специальные символы, новые строки и пустые поля!

Если вам не нужны пустые поля, вы можете:

mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}")   # drop '\n' added by '<<<'

Но вы можете использовать поля через функцию:

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.

Или Удалить новую строку, добавленную <<< Синтаксис bash в функции:

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

Будет отображать тот же результат:

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.

Разделенная строка на основе разделителя в

Но если бы вы написали что-то пригодное для использования под многими оболочками, вы должны не использовать bashisms .

Существует синтаксис, используемый во многих оболочках, для разделения строки на первый или последний вхождение подстроки:

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(Отсутствие этого - главная причина моей публикации ответа;)

Как указано Score_Under :

# и % удаляют самую короткую подходящую строку и

## и %% удаляют самое длинное из возможных.

, где # и ## означают слева (начало) строки и

% и %% означает справа (конец) строки.

Этот небольшой пример скрипта хорошо работает под , , , и также был протестирован в bash Mac-OS :

var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]

Веселись!

128 голосов
/ 27 апреля 2015

Я видел пару ответов, ссылающихся на команду cut, но все они были удалены. Немного странно, что никто не уточнил это, потому что я думаю, что это одна из наиболее полезных команд для такого рода вещей, особенно для анализа файлов журнала с разделителями.

В случае разбиения этого конкретного примера на массив сценариев bash tr, вероятно, более эффективен, но можно использовать cut и более эффективно, если вы хотите извлечь определенные поля из середины. *

Пример:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

Очевидно, что вы можете поместить это в цикл и выполнить итерацию параметра -f для независимого извлечения каждого поля.

Это становится более полезным, когда у вас есть лог-файл с разделителями и строки, подобные этой:

2015-04-27|12345|some action|an attribute|meta data

cut очень удобно для возможности cat этого файла и выбора определенного поля для дальнейшей обработки.

94 голосов
/ 11 августа 2016

Это сработало для меня:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
84 голосов
/ 28 мая 2009

Как насчет этого подхода:

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

Источник

62 голосов
/ 08 сентября 2012

Это также работает:

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

Будьте осторожны, это решение не всегда правильно. Если вы передадите только «bla@some.com», он назначит его как ADD1, так и ADD2.

62 голосов
/ 28 мая 2009
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
38 голосов
/ 14 января 2013

Я думаю, AWK - лучшая и эффективная команда для решения вашей проблемы. AWK включен в Bash по умолчанию почти во всех дистрибутивах Linux.

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

даст

bla@some.com john@home.com

Конечно, вы можете сохранить каждый адрес электронной почты, переопределив поле печати awk.

...