34 Вопрос: Как разделить строку на разделитель в Bash?

вопрос создан в Mon, Oct 22, 2018 12:00 AM

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

 
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, это работает.

    
1758
  1. Что касается вашего "Edit2": вы можете просто "сбросить IFS", и он вернется в состояние по умолчанию. Нет необходимости сохранять и восстанавливать его явно, если только у вас нет причин ожидать, что для него уже установлено значение, отличное от значения по умолчанию. Более того, если вы делаете это внутри функции (а если нет, то почему бы и нет?), Вы можете установить IFS в качестве локальной переменной, и он вернется к своему предыдущему значению после выхода из функции.
    2012-05-01 01: 26: 13Z
  2. @ BrooksMoses: (a) +1 за использование local IFS=..., где это возможно; (b) -1 для unset IFS, это не совсем сбрасывает IFS к его значению по умолчанию, хотя я считаю, что неустановленный IFS ведет себя так же, как значение по умолчанию IFS ($'\t \n'), однако это кажется плохой практикой слепо предполагать, что ваш код никогда не будет вызываться, если для IFS задано пользовательское значение; (c) другая идея состоит в том, чтобы вызвать подоболочку: (IFS=$custom; ...) при выходе из подоболочки IFS вернется к тому, что было изначально.
    2012-05-31 05: 21: 27Z
  3. Я просто хочу быстро взглянуть на пути, чтобы решить, куда бросить исполняемый файл, поэтому я прибег к запуску ruby -e "puts ENV.fetch('PATH').split(':')". Если вы хотите остаться чистым, bash не поможет, но использовать любой язык сценариев со встроенным разделением проще.
    2016-03-07 15: 32: 08Z
  4. for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
    2018-04-26 20: 15: 30Z
  5. Чтобы сохранить его в виде массива, мне пришлось поместить другой набор скобок и изменить \n только на пробел. Итак, последняя строка mails=($(echo $IN | tr ";" " ")). Так что теперь я могу проверить элементы mails, используя обозначение массива mails[index] или просто повторяя цикл
    2018-07-03 14: 08: 33Z
30 ответов                              30                         

Вы можете установить переменную внутреннего разделителя полей (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"
    
1084
2012-03-08 20: 31: 44Z
  1. Вероятно, это лучший способ. Как долго IFS сохранит свое текущее значение, может ли он испортить мой код, будучи установленным, когда его не должно быть, и как я могу сбросить его, когда я закончу с ним?
    2009-05-28 02: 25: 24Z
  2. теперь после применения исправления, только в течение времени выполнения команды чтения:)
    2009-05-28 03: 04: 10Z
  3. Вы можете читать все сразу, не используя цикл while: read -r -d '' -a addr < < < "$in" # Ключ -d '' здесь является ключевым, он говорит, что чтение не следует останавливать на первой новой строке (по умолчанию это -d), а продолжать до EOF или байта NULL (который встречается только в двоичных данных).
    2009-05-28 06: 14: 17Z
  4. @ LucaBorrione Установка IFS в той же строке, что и read без точки с запятой или другого разделителя, в отличие от отдельной команды, определяет область действия этой команды - так что это всегда "восстановлен"; вам не нужно ничего делать вручную.
    2013-07-06 14: 39: 04Z
  5. @ imaginerThis Существует ошибка, включающая в себя управляющие строки и локальные изменения IFS, для которых требуется $IN в кавычках. Ошибка исправлена ​​в bash 4.3.
    2014-10-02 03: 50: 05Z

Взято из Разделенный массив сценариев 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;
858
2017-04-13 12: 36: 28Z
  1. Я просто хочу добавить: это самый простой из всех, вы можете получить доступ к элементам массива с помощью ${arrIN [1]} (конечно, начиная с нулей)
    2011-03-21 18: 50: 09Z
  2. Нашел его: техника изменения переменной внутри ${} известна как «расширение параметра».
    2012-01-05 15: 13: 36Z
  3. Работает ли он, если в исходной строке есть пробелы?
    2013-02-25 09: 12: 10Z
  4. Нет, я не думаю, что это работает, когда присутствуют также пробелы ... это преобразование ',' в '' и затем построение массива, разделенного пробелами .
    2013-04-12 22: 47: 51Z
  5. Это плохой подход по другим причинам: например, если ваша строка содержит ;*;, то * будет расширен до списка имен файлов в текущем каталоге. -1
    2013-07-06 14: 39: 57Z

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

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

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

    
222
2009-05-28 02: 09: 44Z
  1. Вы должны были сохранить ответ IFS. Он научил меня чему-то, чего я не знал, и определенно создал массив, тогда как это просто дешевая замена.
    2009-05-28 02: 42: 44Z
  2. Я вижу. Да, я нахожу себя в этих глупых экспериментах, я буду учиться чему-то новому каждый раз, когда пытаюсь что-то ответить Я отредактировал материал, основанный на обратной связи #bash IRC и восстановленный:)
    2009-05-28 02: 59: 02Z
  3. - 1, вы, очевидно, не знаете о разбиении слов, потому что это приводит к двум ошибкам в вашем коде. один - когда вы не заключаете в кавычки $IN, а другой - когда вы притворяетесь, что новая строка - единственный разделитель, используемый при разделении слов. Вы перебираете каждое WORD в IN, а не каждую строку, и DEFINATELY, а не каждый элемент, разделенный точкой с запятой, хотя может показаться, что побочный эффект выглядит так, как будто он работает.
    2009-05-28 06: 12: 24Z
  4. Вы можете изменить его на echo "$IN" | tr ';' '\n' | пока читаешь -r ADDY; do # process "$ADDY"; Я думаю, что это сделано для того, чтобы ему повезло. Обратите внимание, что это приведет к развороту, и вы не сможете изменить внешние переменные внутри цикла (поэтому я использовал синтаксис < < < "$IN") затем
    2009-05-28 17: 00: 04Z
  5. Подведем итоги обсуждения в комментариях: Предостережения общего пользования : оболочка применяет разбиение слов и расширения до строки, которая может быть нежелательной; просто попробуйте. IN="bla@some.com;john@home.com;*;broken apart". Вкратце: этот подход сломается, если ваши токены содержат встроенные пробелы и /или символы. например, *, для которого токен соответствует именам файлов в текущей папке.
    2013-04-24 14: 13: 57Z

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

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

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

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

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

( версия > = 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>]

или удалите каждое поле после обработки (мне нравится такой подход shifting ):

 
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 :

  

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

     

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

     

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

     

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

Этот небольшой пример скрипта хорошо работает в . , , , busybox и также была протестирована под 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>]

Веселись!

    

163
2018-11-09 13: 06: 10Z
  1. Подстановки #, ##, % и %% имеют то, что IMO проще запомнить (для того, сколько они удаляют): # и % удаляют самое короткое возможное соответствие строка, а ## и %% удаляют самое длинное из возможных.
    2015-04-28 16: 58: 33Z
  2. IFS=\; read -a fields <<<"$var" завершается ошибкой на новых строках и добавляет завершающий перевод новой строки. Другое решение удаляет завершающее пустое поле.
    2016-10-26 04: 36: 52Z
  3. Разделитель оболочки - самый элегантный ответ, точка.
    2017-08-30 17: 50: 16Z
  4. Может ли последняя альтернатива использоваться со списком разделителей полей, установленным где-то еще? Например, я имею в виду использовать его в качестве сценария оболочки и передавать список разделителей полей в качестве позиционного параметра.
    2018-10-04 03: 42: 55Z
  5. Да, в цикле: for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ...
    2018-10-04 07: 47: 13Z

Я видел несколько ответов, относящихся к команде 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 этого файла и выбрать конкретное поле для дальнейшей обработки.

    
129
2015-04-28 22: 17: 52Z
  1. Престижность за использование cut, это правильный инструмент для работы! Гораздо лучше, чем любой из этих хакерских оболочек.
    2016-11-02 08: 42: 39Z
  2. Этот подход будет работать, только если вы заранее знаете количество элементов; вам нужно запрограммировать немного логики вокруг него. Он также запускает внешний инструмент для каждого элемента.
    2017-09-14 08: 30: 06Z
  3. Точно, что я искал, пытаясь избежать пустой строки в CSV. Теперь я также могу указать точное значение столбца. Работа с IFS уже используется в цикле. Лучше, чем ожидалось для моей ситуации.
    2018-05-10 04: 20: 30Z

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

 
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
    
94
2017-01-24 02: 33: 53Z
  1. вырезать работает только с одним символом в качестве разделителя.
    2018-01-08 08: 57: 20Z
  2. Хотя он работает только с одним символом-разделителем, это то, что ищет OP (записи, разделенные точкой с запятой).
    2018-12-12 01: 37: 50Z

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

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

источник р>     

84
2011-07-20 16: 21: 05Z
  1. + 1 ... но я бы не назвал переменную "Array" ... pet peev, наверное. Хорошее решение.
    2011-09-05 01: 06: 06Z
  2. + 1 ... но "устанавливать" и объявлять -a не нужно. Вы могли бы также использовать только IFS";" && Array=($IN)
    2011-11-03 22: 33: 31Z
  3. + 1 Только замечание: не рекомендуется ли сохранить старый IFS, а затем восстановить его? (как показывает stefanB в его edit3) люди, приземляющиеся здесь (иногда просто копирующие и вставляющие решение), могут не думать об этом
    2012-09-03 09: 26: 04Z
  4. - 1: Во-первых, @ata прав, что большинство команд в этом ничего не делают. Во-вторых, он использует разбиение слов для формирования массива и ничего не делает для запрета расширения глобуса при этом (поэтому, если у вас есть символы глобуса в любом из элементов массива, эти элементы заменяются соответствующими именами файлов). /DIV>
    2013-07-06 14: 44: 29Z
  5. Предложите использовать $'...': IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'. Затем echo "${Array[2]}" напечатает строку с новой строкой. set -- "$IN" также необходим в этом случае. Да, чтобы предотвратить глобальное расширение, решение должно включать set -f.
    2016-01-08 12: 29: 54Z
 
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
    
62
2009-05-28 02: 12: 59Z
  1. - 1 что если строка содержит пробелы? , например, IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) создаст в этом случае массив из 8 элементов (элемент для каждого слова разделенный пробелом), а не 2 (элемент для каждой строки разделенный точкой с запятой)
    2012-09-03 10: 08: 01Z
  2. @ Luca Нет, скрипт sed создает ровно две строки. То, что создает несколько записей для вас, - это когда вы помещаете их в массив bash (который по умолчанию разделяется на пробелы)
    2012-09-03 17: 33: 50Z
  3. В этом и заключается суть: операционному оператору необходимо хранить записи в массиве, чтобы проходить по нему, как вы можете видеть из его правок. Я думаю, что ваш (хороший) ответ не упомянул об использовании arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) для достижения этой цели и совете изменить IFS на IFS=$'\n' для тех, кто приземлится здесь в будущем и должен разбить строку, содержащую пробелы. (и восстановить его потом). :)
    2012-09-04 07: 09: 57Z
  4. @ Luca Хороший вопрос. Однако, когда я написал этот ответ, назначения массива не было в первоначальном вопросе.
    2012-09-04 16: 55: 10Z

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

 
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
2014-04-17 01: 39: 20Z
  1. Вы можете использовать -s, чтобы избежать упомянутой проблемы: superuser.com/questions/896800/… " -f, --fields = LIST выбрать только эти поля; также вывести любую строку который не содержит символа-разделителя, если не указана опция -s "
    2016-03-03 17: 17: 52Z

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

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

даст

 
bla@some.com john@home.com

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

    
38
2015-04-19 22: 26: 52Z
  1. Или еще проще: echo "bla@some.com; john@home.com" | awk 'BEGIN {RS = ";"} {print}'
    2014-01-07 21: 30: 24Z
  2. @ Jaro Это отлично сработало, когда у меня была строка с запятыми, и мне нужно было переформатировать ее в строки. Спасибо.
    2014-05-06 21: 58: 29Z
  3. В этом сценарии это сработало - > "echo" $SPLIT_0 "| awk -F 'inode =' '{print $1}'"! У меня возникли проблемы при попытке использовать символы ("inode =") вместо символов (";"). $1, $2, $3, $4 устанавливаются как позиции в массиве! Если есть способ установить массив ... лучше! Спасибо!
    2015-08-05 12: 59: 21Z
  4. @ EduardoLucio, что я думаю о том, может быть, вы можете сначала заменить свой разделитель inode= на ;, например, на sed -i 's/inode\=/\;/g' your_file_to_process, а затем определить -F';', когда примените awk, надеюсь, что это может помочь вам.
    2015-08-06 02: 42: 02Z

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

 
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
    
30
2017-05-23 12: 34: 44Z
  1. Это не работает.
    2011-09-10 00: 31: 16Z
  2. Я думаю, что это так! Запустите указанные выше команды, а затем «echo $ADDR1 ... $ADDR2», и я получу «bla@some.com ... john@home.com» выход
    2011-10-06 15: 33: 48Z
  3. Это действительно ДЕЙСТВИТЕЛЬНО хорошо для меня ... Я использовал его, чтобы перебирать массив строк, которые содержали разделенные запятыми данные DB, SERVER, PORT для использования mysqldump.
    2011-10-28 14: 36: 47Z
  4. Диагноз: назначение IFS=";" существует только в подоболочке $(...; echo $IN); Вот почему некоторые читатели (включая меня) изначально думают, что это не сработает. Я предположил, что все $IN были подбиты ADDR1. Но ник это правильно; это работает. Причина в том, что команда echo $IN анализирует свои аргументы, используя текущее значение $IFS, но затем выводит их на стандартный вывод с использованием разделителя пробелов, независимо от значения параметра $IFS. Таким образом, чистый эффект такой, как если бы вы назвали read ADDR1 ADDR2 <<< "bla@some.com john@home.com" (обратите внимание, что ввод не разделен пробелом; -отделен).
    2012-05-31 05: 28: 59Z
  5. Это не работает с пробелами и символами новой строки, а также расширяет символы подстановки * в echo $IN с расширением без кавычек.
    2016-10-26 04: 43: 25Z

В 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")
    
26
2015-10-27 16: 03: 48Z
  1. Я нашел его как редкое решение в этом списке, которое корректно работает с \n, пробелами и * одновременно. Также нет петель; Переменная массива доступна в оболочке после выполнения (в отличие от ответа с наибольшим количеством голосов). Обратите внимание, in=$'...', он не работает с двойными кавычками. Я думаю, что нужно больше голосов.
    2016-01-08 12: 10: 43Z

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

 
IFS=';' read ADDR1 ADDR2 <<<$IN
    
21
2010-09-13 20: 10: 42Z
  1. Попробуйте использовать read -r ..., чтобы, например, два символа «\t» во входных данных оказались одинаковыми двумя символами в переменных (вместо одного табуляция).
    2012-05-31 05: 36: 47Z
  2. - 1 Это не работает здесь (Ubuntu 12.04). Добавление echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2" к вашему фрагменту приведет к получению ADDR1 bla@some.com john@home.com\nADDR2 (\n - новая строка)
    2012-09-03 10: 07: 29Z
  3. Это, вероятно, связано с ошибкой, связанной с IFS, и здесь строки, которые были исправлены в bash 4.3. Цитирование $IN должно исправить это. это означает, что в кавычках не должно быть необходимости. Однако даже в 4.3 осталась, по крайней мере, одна ошибка - сообщенная и запланированная на исправление - так что цитирование остается хорошей идеей.)
    2015-09-19 13: 59: 33Z
  4. Это прерывается, если $in содержит символы новой строки, даже если $IN указан в кавычках. И добавляет завершающий перевод строки.
    2016-10-26 04: 55: 59Z

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

 $IN

где

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done
слов с разделителями на основе разделителя и IFS используются для создания массива , Затем () используется для возврата каждого элемента как отдельного слова.

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

    
19
2016-10-26 10: 26: 38Z
  1. Использование unset IFS без кавычек позволяет расширять символы подстановки.
    2016-10-26 05: 03: 02Z

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

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

 $in

вы получите:

 
a="foo:bar"
b=${a%:*}
c=${a##*:}
    
16
2016-08-01 13: 15: 07Z

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

 
b = foo
c = bar

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

 
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 в Bash и -a в zsh.

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

 -A     
9
2017-06-13 18: 24: 31Z
  1. работает и аккуратно модульно.
    2017-10-23 16: 10: 27Z
  2. Кажется, не работает с разделителями длиннее 1 символа: split = $(split "$content" "file: //")
    2019-06-14 05: 23: 30Z
  3. True - из
    #!/usr/bin/env bash
    
    split() {
        # ...
    }
    
    split "$@"
    
    : help read
    2019-06-14 18: 52: 55Z

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

 -d delim continue until the first character of DELIM is read, rather than newline

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

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

затем р>  

brew install findutils
    
7
2015-09-16 03: 34: 51Z

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

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

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

 
echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'
    
6
2018-01-21 11: 34: 13Z

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

 
echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"
    
5
2012-02-28 08: 18: 47Z

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

 
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

Теперь

IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";
, ${a[0]} и т. д. соответствуют ожиданиям. Используйте ${a[1]} для количества терминов. Или, конечно, повторить:  ${#a[*]}

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

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

for i in ${a[*]}; do echo $i; done
.     
4
2017-01-21 20: 50: 45Z
  1. Не работает, если $IFS содержит более двух адресов электронной почты. Обратитесь к той же идее (но исправленной) на ответ Палиндрома
    2013-10-07 13: 33: 38Z
  2. Лучше использовать IN (двойная косая черта), чтобы он также работал с более чем двумя значениями. Помните, что любой подстановочный знак (${IN//;/ }) будет расширен. И завершающее пустое поле будет отброшено.
    2016-10-26 05: 14: 58Z
 *?[

Выход р>  

IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

Система: Ubuntu 12.04.1

    
3
2016-10-25 12: 55: 51Z
  1. IFS не устанавливается здесь в конкретном контексте
    bla@some.com
    john@home.com
    
    и, следовательно, может нарушить остальную часть кода, если таковой имеется.
    2017-01-02 05: 37: 09Z

Если места нет, почему бы и нет?

 read     
2
2013-04-24 13: 13: 57Z

Две альтернативы bourne-ish, где ни один не требует массивов bash:

Случай 1 . Делайте все просто и красиво: используйте NewLine в качестве разделителя записей ... например.

 
IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

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

Идея: может быть, стоит использовать NL экстенсивно для внутреннего использования и преобразовывать его в другой RS только при генерации окончательного результата извне .

Случай 2 : использование ";" в качестве разделителя записей ... например.

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

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

В обоих случаях под-список может быть составлен в цикле постоянным после завершения цикла. Это полезно при работе со списками в памяти, вместо хранения списков в файлах. {Приписка сохраняй спокойствие и продолжай B-)}

    
2
2013-09-02 06: 45: 57Z

Помимо фантастических ответов, которые уже были предоставлены, если это просто вопрос распечатки данных, вы можете использовать

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"
:  awk

Это устанавливает разделитель полей на

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
, чтобы он мог перебирать поля с помощью цикла ; и печатать соответственно.

Test

 for

С другим входом:

 
$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]
    
2
2015-01-08 10: 21: 45Z
Дел>

Используйте встроенную

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]
для загрузки массива set:  $@

Тогда пусть вечеринка начнется:

 
IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'
    
1
2013-04-30 03: 10: 43Z
  1. Лучше использовать
    echo $#
    for a; do echo $a; done
    ADDR1=$1 ADDR2=$2
    
    , чтобы избежать некоторых проблем с $IN, начинающимся с тире. Тем не менее, расширение без кавычек set -- $IN будет расширять подстановочные знаки ($IN).
    2016-10-26 05: 17: 41Z

В оболочке Android большинство предложенных методов просто не работают:

 *?[

Что работает, так это

 
$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

где

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin
означает глобальную замену.     
1
2015-04-19 22: 27: 16Z
  1. Сбой, если какая-либо часть $PATH содержит пробелы (или символы новой строки). Также расширяются подстановочные знаки (звездочка *, знак вопроса? И фигурные скобки […]).
    2016-10-26 05: 08: 13Z
 //

Вывод:

 
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

Объяснение: Простое присваивание с использованием круглых скобок () преобразует список, разделенный точкой с запятой, в массив, если при этом у вас есть правильный IFS. Стандартный цикл FOR обрабатывает отдельные элементы в этом массиве как обычно. Обратите внимание, что список, заданный для переменной IN, должен быть «жестко» заключен в кавычки, то есть с одиночными тиками.

IFS должен быть сохранен и восстановлен, так как Bash не обрабатывает назначение так же, как команда. Альтернативный обходной путь - обернуть назначение внутри функции и вызвать эту функцию с измененным IFS. В этом случае отдельное сохранение /восстановление IFS не требуется. Спасибо за "Бизе" за указание на это.

    
1
2015-04-19 22: 28: 46Z
  1. bla@some.com
    john@home.com
    Charlie Brown <cbrown@acme.com
    !"#$%&/()[]{}*? are no problem
    simple is beautiful :-)
    
    хорошо ... не совсем: !"#$%&/()[]{}*? are no problem являются символами глобуса. Так как насчет создания этого каталога и файла: `mkdir '!" # $% &Amp;'; touch '! "# $% &Amp; /() [] {} у вас есть хахахаха - нет проблем' и выполнение вашей команды? простой может быть красивым, но когда он сломан, он сломан.
    2015-02-20 16: 45: 28Z
  2. @ gniourf_gniourf Строка хранится в переменной. Пожалуйста, посмотрите оригинальный вопрос.
    2015-02-25 07: 20: 48Z
  3. @ ajaaskel Вы не полностью поняли мой комментарий. Перейдите в каталог с нулями и введите следующие команды: []*?. Я должен признать, что они будут создавать только каталог и файл со странными названиями. Затем выполните ваши команды с точным mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem', который вы дали: IN. Вы увидите, что вы не получите ожидаемый результат. Потому что вы используете метод, подверженный раскрытию пути, чтобы разбить вашу строку.
    2015-02-25 07: 26: 44Z
  4. Это демонстрирует, что символы IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)', *, ? и даже, если установлено [...], extglob, !(...), @(...), ?(...) являются проблемы с этим методом!
    2015-02-25 07: 29: 48Z
  5. @ gniourf_gniourf Спасибо за подробные комментарии по поводу глобализации. Я изменил код, чтобы отключить. Однако я хотел показать, что довольно простое назначение может выполнять работу по разделению.
    2015-02-26 15: 26: 03Z

Ладно, ребята!

Вот мой ответ

 +(...)

Почему этот подход "лучший" для меня?

По двум причинам:

  1. Вам не нужно экранировать разделитель;
  2. У вас не будет проблемы с пробелами . Значение будет правильно разделено в массиве!

[] 's р>     

1
2016-04-04 20: 22: 07Z
  1. FYI,
    DELIMITER_VAL='='
    
    read -d '' F_ABOUT_DISTRO_R <<"EOF"
    DISTRIB_ID=Ubuntu
    DISTRIB_RELEASE=14.04
    DISTRIB_CODENAME=trusty
    DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
    NAME="Ubuntu"
    VERSION="14.04.4 LTS, Trusty Tahr"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 14.04.4 LTS"
    VERSION_ID="14.04"
    HOME_URL="http://www.ubuntu.com/"
    SUPPORT_URL="http://help.ubuntu.com/"
    BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
    EOF
    
    SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
    while read -r line; do
       SPLIT+=("$line")
    done <<< "$SPLIT_NOW"
    for i in "${SPLIT[@]}"; do
        echo "$i"
    done
    
    и /etc/os-release предназначены для получения, а не для анализа. Таким образом, ваш метод действительно неверен. Более того, вы не совсем отвечаете на вопрос о разбросе строки по разделителю.
    2017-01-30 08: 26: 41Z

Однострочный для разделения строки, разделенной ';' в массив:

 /etc/lsb-release

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

    
0
2014-11-29 22: 02: 13Z
  1. - 1 здесь это не работает (Ubuntu 12.04). он печатает только первый эхо со всеми значениями $IN, в то время как второй пустой. Вы можете увидеть это, если поставить echo "0:" ${ADDRS [0]} \n echo "1:" ${ADDRS [1]}, на выходе будет
    IN="bla@some.com;john@home.com"
    ADDRS=( $(IFS=";" echo "$IN") )
    echo ${ADDRS[0]}
    echo ${ADDRS[1]}
    
    (\n - новая строка)
    2012-09-03 10: 04: 23Z
  2. пожалуйста, обратитесь к ответу nickjb для рабочей альтернативы этой идее stackoverflow.com/a/6583589/1032370
    2012-09-03 10: 05: 06Z
  3. - 1, 1. IFS не устанавливается в этом подоболочке (он передается в среду "echo", которая является встроенной, поэтому ничего не происходит тем не мение). 2. 0: bla@some.com;john@home.com\n 1: котируется, поэтому не подлежит разделению IFS. 3. Подстановка процесса разделяется пробелами, но это может привести к повреждению исходных данных.
    2015-04-28 17: 09: 39Z

Возможно, не самое элегантное решение, но работает с $IN и пробелами:

 *

Выходы р>  

IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

Другой пример (разделители в начале и в конце):

 
> [bla@so me.com]
> [*]
> [john@home.com]

По сути, он удаляет все символы, кроме

IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []
, что составляет ;, например. delims. Затем выполняется цикл ;;; с for до 1, который считается number-of-delimiters. Последний шаг - безопасно получить ${#delims}-ю часть, используя $i.     
0
2016-02-26 12: 20: 31Z
cut
источник размещен Вот