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

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

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

28.05.2009 02:03:43
Что касается вашего «Edit2»: вы можете просто «сбросить IFS», и он вернется в состояние по умолчанию. Нет необходимости сохранять и восстанавливать его явно, если только у вас нет причин ожидать, что для него уже установлено значение не по умолчанию. Более того, если вы делаете это внутри функции (а если нет, то почему бы и нет?), Вы можете установить IFS в качестве локальной переменной, и он вернется к своему предыдущему значению после выхода из функции.
Brooks Moses 1.05.2012 01:26:13
@BrooksMoses: (a) +1 за использование, local IFS=...где это возможно; (b) -1 для unset IFS, это не совсем сбрасывает IFS к его значению по умолчанию, хотя я считаю, что неустановленный IFS ведет себя так же, как значение по умолчанию IFS ($ '\ t \ n'), однако это кажется плохой практикой слепо предполагать, что ваш код никогда не будет вызываться, если для IFS задано пользовательское значение; (c) другая идея состоит в том, чтобы вызывать подоболочку: (IFS=$custom; ...)при выходе из подоболочки IFS вернется к тому, что было изначально.
dubiousjim 31.05.2012 05:21:27
Я просто хочу быстро взглянуть на пути, чтобы решить, куда бросить исполняемый файл, поэтому я прибег к запуску ruby -e "puts ENV.fetch('PATH').split(':')". Если вы хотите остаться чистым, bash не поможет, но проще использовать любой язык сценариев со встроенным разделением.
nicooga 7.03.2016 15:32:08
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
user2037659 26.04.2018 20:15:30
Чтобы сохранить его в виде массива, я должен был поставить еще один набор скобок и изменить его \nтолько на пробел. Итак, последняя строка mails=($(echo $IN | tr ";" " ")). Так что теперь я могу проверять элементы mails, используя нотацию массива mails[index]или просто повторяя в цикле
afranques 3.07.2018 14:08:33
30 ОТВЕТОВ
РЕШЕНИЕ

Вы можете установить переменную внутреннего разделителя полей (IFS), а затем разрешить ее анализ в массив. Когда это происходит в команде, тогда присваивание IFSпроисходит только среде этой отдельной команды (to 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"
1214
8.03.2012 20:31:44
Это, наверное, лучший способ. Как долго IFS сохранит свое текущее значение, сможет ли он испортить мой код, будучи установленным, когда его не должно быть, и как я могу сбросить его, когда я закончу с этим?
Chris Lutz 28.05.2009 02:25:24
теперь после применения исправления, только в течение срока действия команды чтения :)
Johannes Schaub - litb 28.05.2009 03:04:10
Вы можете прочитать все сразу, не используя цикл while: read -r -d '' -a addr <<< "$ in" # Ключ -d '' здесь ключевой, он сообщает read не останавливаться на первой новой строке ( который является значением по умолчанию -d), но продолжается до EOF или пустого байта (который встречается только в двоичных данных).
lhunath 28.05.2009 06:14:17
@LucaBorrione Установка IFSв той же строке, что и readбез точки с запятой или другого разделителя, в отличие от отдельной команды, ограничивает ее этой командой, поэтому она всегда «восстанавливается»; вам не нужно ничего делать вручную.
Charles Duffy 6.07.2013 14:39:04
@imagineerThis Существует ошибка, связанная с приведенными здесь строками и локальными изменениями в IFS, которые необходимо $INзаключать в кавычки. Ошибка исправлена ​​в bash4.3.
chepner 2.10.2014 03:50:05

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

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

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

246
28.05.2009 02:09:44
Вы должны были сохранить ответ IFS. Он научил меня чему-то, чего я не знал, и определенно создал массив, тогда как это просто дешевая замена.
Chris Lutz 28.05.2009 02:42:44
Понимаю. Да, я нахожу себя в этих глупых экспериментах, я буду учиться чему-то новому каждый раз, когда пытаюсь что-то ответить. Я отредактировал материал, основанный на обратной связи #bash IRC и восстановленный :)
Johannes Schaub - litb 28.05.2009 02:59:02
-1, вы явно не знаете о разбиении слов, потому что это вносит две ошибки в ваш код. один - когда вы не заключаете в кавычки $ IN, а другой - когда вы притворяетесь, что новая строка - единственный разделитель, используемый в разбиении слов. Вы перебираете каждое WORD в IN, а не каждую строку, и ОПРЕДЕЛЕННО не каждый элемент, разделенный точкой с запятой, хотя может показаться, что побочный эффект выглядит так, как будто он работает.
lhunath 28.05.2009 06:12:24
Вы можете изменить его на «$ IN» | tr ';' '\ n' | пока читаешь -r ADDY; do # process "$ ADDY"; я думаю, что это сделано для того, чтобы ему повезло :) Обратите внимание, что это приведет к развороту, и вы не сможете изменить внешние переменные внутри цикла (поэтому я использовал синтаксис <<< "$ IN"), затем
Johannes Schaub - litb 28.05.2009 17:00:04
Подводя итоги обсуждения в комментариях: Предостережения для общего пользования : оболочка применяет разбиение слов и расширения к строке, что может быть нежелательным; просто попробуйте. IN="bla@some.com;john@home.com;*;broken apart", Вкратце: этот подход сломается, если ваши токены содержат встроенные пробелы и / или символы. например *, чтобы токен совпадал с именами файлов в текущей папке.
mklement0 24.04.2013 14:13:57
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
65
28.05.2009 02:12:59
-1 что если строка содержит пробелы? например IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ), в этом случае будет получен массив из 8 элементов (элемент для каждого разделенного пробелом слова), а не 2 (элемент для каждой строки, разделенной
Luca Borrione 3.09.2012 10:08:01
@ Luca Нет, скрипт sed создает ровно две строки. Что создает несколько записей для вас , когда вы поместите его в массив Баша (который расщепляется на белом пространстве по умолчанию)
lothar 3.09.2012 17:33:50
В том-то и дело: ОП должен хранить записи в массиве, чтобы зацикливаться на нем, как вы можете видеть из его правок. Я думаю, что ваш (хороший) ответ упущен, чтобы упомянуть, чтобы использовать его arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )для достижения этой цели, а также посоветовать сменить IFS IFS=$'\n'на тех, кто приземлится здесь в будущем и должен разбить строку, содержащую пробелы. (и восстановить его потом). :)
Luca Borrione 4.09.2012 07:09:57
@ Лука Хороший вопрос. Однако, когда я написал этот ответ, назначения массива не было в первоначальном вопросе.
lothar 4.09.2012 16:55:10

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

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

Источник

89
20.07.2011 16:21:05
+1 ... но я бы не назвал переменную "Array" ... пэт пев, наверное. Хорошее решение
Yzmir Ramirez 5.09.2011 01:06:06
+1 ... но "установить" и объявить -a не нужны. Вы могли бы, а использовали толькоIFS";" && Array=($IN)
ata 3.11.2011 22:33:31
+1 Только примечание: не рекомендуется ли сохранить старый IFS, а затем восстановить его? (как показывает stefanB в его edit3) люди, приземляющиеся здесь (иногда просто копирующие и вставляющие решение), могут не думать об этом
Luca Borrione 3.09.2012 09:26:04
-1: во-первых, @ata прав, что большинство команд в этом ничего не делают. Во-вторых, он использует разбиение слов для формирования массива и ничего не делает для запрета расширения глобуса при этом (поэтому, если у вас есть символы глобуса в любом из элементов массива, эти элементы заменяются соответствующими именами файлов).
Charles Duffy 6.07.2013 14:44:29
Предлагайте использованию $'...': IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'. Затем echo "${Array[2]}"напечатает строку с новой строкой. set -- "$IN"Также необходимо в этом случае. Да, чтобы предотвратить глобальное расширение, решение должно включать set -f.
John_West 8.01.2016 12:29:54

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

IFS=';' read ADDR1 ADDR2 <<<$IN
28
13.09.2010 20:10:42
Попробуйте использовать, read -r ...чтобы, например, два символа «\ t» во входных данных заканчивались одинаковыми двумя символами в ваших переменных (вместо одного символа табуляции).
dubiousjim 31.05.2012 05:36:47
-1 Это не работает здесь (Ubuntu 12.04). Добавление echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"к вашему фрагменту приведет к выводу ADDR1 bla@some.com john@home.com\nADDR2(\ n -
Luca Borrione 3.09.2012 10:07:29
Вероятно, это связано с ошибкой, включающей IFSи здесь строки, которые были исправлены в bash4.3. Цитирование $INдолжно исправить это. (Теоретически, $INне подлежит разделению или смещению слова после его расширения, что означает, что кавычки не должны быть необходимыми. Даже в 4.3, тем не менее, остается по крайней мере одна ошибка - сообщается и планируется исправить - поэтому цитирование остается хорошим идея.)
chepner 19.09.2015 13:59:33
Это ломается, если $ in содержит символы новой строки, даже если $ IN указан в кавычках. И добавляет завершающий перевод строки.
Isaac 26.10.2016 04:55:59
Проблема с этим и многими другими решениями также заключается в том, что предполагается, что в $ IN - ТОЛЬКО ДВУХ элементов, и что вы хотите, чтобы второй и последующие элементы были объединены в ADDR2. Я понимаю, что это отвечает требованиям, но это бомба замедленного действия.
Steven the Easily Amused 1.09.2019 14:36:44

Взято из скриптового массива 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;
957
13.04.2017 12:36:28
Я просто хочу добавить: это самый простой из всех, вы можете получить доступ к элементам массива с помощью $ {arrIN [1]} (конечно, начиная с нулей)
Oz123 21.03.2011 18:50:09
Нашел его: техника изменения переменной внутри $ {} известна как «расширение параметров».
KomodoDave 5.01.2012 15:13:36
Нет, я не думаю, что это работает, когда присутствуют также пробелы ... это преобразование ',' в '', а затем построение массива, разделенного пробелами.
Ethan 12.04.2013 22:47:51
Очень краткий, но есть предостережения для общего использования : оболочка применяет разбиение слов и расширения к строке, что может быть нежелательным; просто попробуйте. IN="bla@some.com;john@home.com;*;broken apart", Вкратце: этот подход сломается, если ваши токены содержат встроенные пробелы и / или символы. например *, чтобы токен совпадал с именами файлов в текущей папке.
mklement0 24.04.2013 14:08:36
Это плохой подход по другим причинам: например, если ваша строка содержит ;*;, то *он будет расширен до списка имен файлов в текущем каталоге. -1
Charles Duffy 6.07.2013 14:39:57

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

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
34
23.05.2017 12:34:44
Я думаю, что это так! Запустите приведенные выше команды, а затем «echo $ ADDR1 ... $ ADDR2», и я получу вывод «bla@some.com ... john@home.com»
nickjb 6.10.2011 15:33:48
Это работает очень хорошо для меня ... Я использовал его, чтобы перебрать массив строк, который содержал разделенные запятыми данные DB, SERVER, PORT для использования mysqldump.
Nick 28.10.2011 14:36:47
Диагноз: IFS=";"назначение существует только в $(...; echo $IN)подоболочке; Вот почему некоторые читатели (включая меня) изначально думают, что это не сработает. Я предположил, что весь $ IN был подбит ADDR1. Но ник это правильно; это работает. Причина в том, что echo $INкоманда анализирует свои аргументы, используя текущее значение $ IFS, но затем выводит их на стандартный вывод, используя разделитель пробелов, независимо от значения $ IFS. Таким образом, чистый эффект такой, как если бы он звонил read ADDR1 ADDR2 <<< "bla@some.com john@home.com"(обратите внимание, что ввод не разделен пробелом; -отделен).
dubiousjim 31.05.2012 05:28:59
Это не будет работать на пространствах и переводы строк, а также расширить символы *в echo $INс расширением некотируемого переменной.
Isaac 26.10.2016 04:43:25
Мне очень нравится это решение. Описание того, почему это работает, было бы очень полезно и сделало бы его лучший общий ответ.
Michael Gaskill 30.01.2017 02:28:51

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

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
4
28.02.2012 08:18:47

Есть два простых метода:

cat "text1;text2;text3" | tr " " "\n"

а также

cat "text1;text2;text3" | sed -e 's/ /\n/g'
-6
8.03.2012 20:37:50
Я думаю, что вы catи echoзапутались. catчитает из файлов. echoчитает текст, данный.
daboross 29.06.2013 20:58:03

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

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

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

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

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

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.

65
17.04.2014 01:39:20
Вы можете использовать -s, чтобы избежать упомянутой проблемы: superuser.com/questions/896800/… "-f, --fields = LIST выбрать только эти поля; также вывести любую строку, которая не содержит символ разделителя, если опция -s не указано "
fersarr 3.03.2016 17:17:52

Здесь есть несколько классных ответов (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решением (ями) в этом случае.

4
21.01.2017 20:50:45
Не работает, если INсодержит более двух адресов электронной почты. Пожалуйста, обратитесь к той же идее (но исправлено) в ответе Палиндрома
olibre 7.10.2013 13:33:38
Лучше использовать ${IN//;/ }(двойной слеш), чтобы он также работал с более чем двумя значениями. Помните, что любой подстановочный знак ( *?[) будет расширен. И конечное пустое поле будет отброшено.
Isaac 26.10.2016 05:14:58

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

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

дам

bla@some.com john@home.com

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

75
3.07.2019 13:15:57
Или еще проще: echo "bla@some.com; john@home.com" | awk 'BEGIN {RS = ";"} {print}'
Jaro 7.01.2014 21:30:24
@Jaro Это отлично сработало, когда у меня была строка с запятыми, и мне нужно было переформатировать ее в строки. Спасибо.
Aquarelle 6.05.2014 21:58:29
В этом сценарии это сработало -> "echo" $ SPLIT_0 "| awk -F 'inode =' '{print $ 1}'"! У меня возникли проблемы при попытке использовать символы ("inode =") вместо символов (";"). $ 1, $ 2, $ 3, $ 4 устанавливаются как позиции в массиве! Если есть способ установить массив ... лучше! Спасибо!
Eduardo Lucio 5.08.2015 12:59:21
@EduardoLucio, что я думаю о том , может быть , вы можете сначала заменить разделитель inode=в ;, например , с помощью sed -i 's/inode\=/\;/g' your_file_to_process, а затем определить , -F';'когда применять awk, надежду на то, что может помочь вам.
Tong 6.08.2015 02:42:02

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

Есть много разных способов сделать это в .

Тем не менее, важно сначала отметить, что он bashимеет много специальных функций (так называемых bashisms ), которые не будут работать в любой другой .

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

Например: в моем Debian GNU / Linux есть стандартная оболочка, которая называется ; Я знаю многих людей, которые любят использовать другую оболочку под названием ; и есть также специальный инструмент под названием с его собственным интерпретатором оболочки ( ).

Запрашиваемая строка

Строка, которая будет разбита в приведенном выше вопросе:

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), , и «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>]

Радоваться, веселиться!

195
28.03.2020 08:57:00
В #, ##, %и %%замены есть то , что ИМО проще объяснение , чтобы помнить (за сколько они удалить) #и %удалить кратчайшую строку соответствия, а также ##и %%удалить самое длинные возможное.
Score_Under 28.04.2015 16:58:33
IFS=\; read -a fields <<<"$var"Терпит неудачу на переводы строк и добавить символ новой строки. Другое решение удаляет завершающее пустое поле.
Isaac 26.10.2016 04:36:52
Разделитель оболочки - самый элегантный ответ, точка.
Eric Chen 30.08.2017 17:50:16
Может ли последняя альтернатива использоваться со списком разделителей полей, установленным где-то еще? Например, я имею в виду использовать это в качестве сценария оболочки и передавать список разделителей полей в качестве позиционного параметра.
sancho.s ReinstateMonicaCellio 4.10.2018 03:42:55
Да, в цикле:for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ...
F. Hauri 4.10.2018 07:47:13

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

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

echo ${arr[0]}
echo ${arr[1]}
2
24.04.2013 13:13:57

Используйте setвстроенный для загрузки $@массива:

IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'

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

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
2
30.04.2013 03:10:43
Лучше использовать, set -- $INчтобы избежать некоторых проблем с "$ IN", начинающимся с тире. Тем не менее, расширение без кавычек $INбудет расширять символы подстановки ( *?[).
Isaac 26.10.2016 05:17:41

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

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

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

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

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

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

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

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"

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

2
2.09.2013 06:45:57

В 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(aka readarray) поддерживает -dвозможность указания разделителя. Отсюда и другой канонический способ:

mapfile -d ';' -t array < <(printf '%s;' "$in")
31
27.10.2015 16:03:48
Я нашел его как редкое решение в этом списке, которое правильно работает с \nпробелами и *одновременно. Также нет петель; Переменная массива доступна в оболочке после выполнения (в отличие от ответа с наибольшим количеством голосов). Обратите внимание, in=$'...'что он не работает с двойными кавычками. Я думаю, что нужно больше голосов.
John_West 8.01.2016 12:10:43
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

Вывод:

bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

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

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

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

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

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

Это устанавливает разделитель полей на ;, чтобы он мог проходить по полям с помощью 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]

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

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]
2
8.01.2015 10:21:45

В оболочке 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

где //означает глобальную замену.

2
19.04.2015 22:27:16
Сбой, если любая часть $ PATH содержит пробелы (или символы новой строки). Также расширяются подстановочные знаки (звездочка *, знак вопроса? И фигурные скобки […]).
Isaac 26.10.2016 05:08:13

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

177
28.04.2015 22:17:52
Слава за использование cut, это правильный инструмент для работы! Очищено намного больше, чем любой из этих взломов.
MisterMiyagi 2.11.2016 08:42:39
Этот подход будет работать, только если вы заранее знаете количество элементов; вам нужно запрограммировать немного логики вокруг него. Он также запускает внешний инструмент для каждого элемента.
uli42 14.09.2017 08:30:06
Точно, что я искал, пытаясь избежать пустой строки в CSV. Теперь я также могу указать точное значение столбца. Работа с IFS уже используется в цикле. Лучше, чем ожидалось для моей ситуации.
Louis Loudog Trottier 10.05.2018 04:20:30
Очень полезно для извлечения идентификаторов и PID тоже, например
Milos Grujic 21.10.2019 09:07:43
Этот ответ стоит прокрутить вниз на полстраницы :)
Gucu112 3.01.2020 17:26:01

Вот чистый 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.

19
26.10.2016 10:26:38
Использование без $inкавычек позволяет расширять символы подстановки.
Isaac 26.10.2016 05:03:02

Есть простой и умный способ, как это:

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 {}
7
16.09.2015 03:34:51

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

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использование cut.

0
26.02.2016 12:20:31

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

Вот мой ответ!

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

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

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

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

[] 'S

1
4.04.2016 20:22:07
К вашему сведению, /etc/os-releaseи /etc/lsb-releaseдолжны быть получены, а не проанализированы. Таким образом, ваш метод действительно неверен. Более того, вы не совсем отвечаете на вопрос о разбросе строки по разделителю.
gniourf_gniourf 30.01.2017 08:26:41

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

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

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

ты получишь:

b = foo
c = bar
20
1.08.2016 13:15:07

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

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
121
24.01.2017 02:33:53
Хотя он работает только с одним символом-разделителем, это то, что ищет OP (записи, разделенные точкой с запятой).
GuyPaddock 12.12.2018 01:37:50
Ответил около четырех лет назад @Ashok , а также, более чем один год назад @DougW , чем ваш ответ, с еще больше информации. Пожалуйста, опубликуйте другое решение, чем у других.
MAChitgarha 3.04.2020 09:41:34
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

Вывод

bla@some.com
john@home.com

Система: Ubuntu 12.04.1

3
25.10.2016 12:55:51
IFS не устанавливается в конкретном контексте readздесь и, следовательно, он может расстроить остальную часть кода, если таковой имеется.
codeforester 2.01.2017 05:37:09

Следующая функция 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 и -Azsh.

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

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"
10
13.06.2017 18:24:31
Кажется, не работает с разделителями длиннее 1 символа: split = $ (split "$ content" "file: //")
madprops 14.06.2019 05:23:30
Правда - от help read:-d delim continue until the first character of DELIM is read, rather than newline
Halle Knast 14.06.2019 18:52:55