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

1835
задан 23.10.2018, 10:20

15 ответов

Две альтернативы 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"

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

2
ответ дан 04.10.2019, 10:25

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

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

Затем, пусть партия начинается:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
\t\n'

Затем, пусть партия начинается:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
1
ответ дан 04.10.2019, 10:25

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

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

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

0
ответ дан 04.10.2019, 10:25
  • 1
    - 1 этот doesn' t работают здесь (человечность 12.04). это печатает только первое эхо со всем значением $IN в нем, в то время как второе пусто. Вы видите его при помещении эха " 0: " $ {ADDRS [0] }\\n повторяют " 1: " $ {ADDRS [1]}, который вывод 0: bla@some.com;john@home.com\n 1: (\n новая строка), – Luca Borrione 04.09.2012, 00:04
  • 2
    обратитесь к nickjb' s отвечают в за рабочую альтернативу этой идее stackoverflow.com/a/6583589/1032370 – Luca Borrione 04.09.2012, 00:05

Здесь есть несколько интересных ответов (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
ответ дан 04.10.2019, 10:25
  • 1
    Не работает, когда IN содержит больше чем два адреса электронной почты. См. ту же идею (но зафиксированный) в palindrom' s ответ – olibre 08.10.2013, 03:33

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

143
ответ дан 04.10.2019, 10:25

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

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

Источник

84
ответ дан 04.10.2019, 10:25
  • 1
    +1..., но я wouldn' t называют переменную " Array"... домашнее животное peev я предполагаю. Хорошее решение. – Yzmir Ramirez 05.09.2011, 15:06
  • 2
    +1..., но " set" и объявите, что-a являются ненужными. Вы, возможно, также использовали всего IFS";" && Array=($IN) – ata 04.11.2011, 11:33
  • 3
    +1 Только примечание стороны: shouldn' t это быть рекомендуемым, чтобы сохранить старую IFS и затем восстановить его? (как показано stefanB в его edit3), люди, приземляющиеся здесь (иногда просто копирование и вставка решения), не могли бы думать об этом – Luca Borrione 03.09.2012, 23:26
  • 4
    - 1: Во-первых, @ata является правильным, что большинство команд в этом ничего не делает. Во-вторых, это использует разделение слова для формирования массива и doesn' t делают что-либо для запрещения расширения шарика при выполнении так (поэтому, если у Вас есть символы шарика в каком-либо из элементов массива, те элементы заменяются соответствием именам файлов). – Charles Duffy 07.07.2013, 04:44

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

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
102
ответ дан 04.10.2019, 10:25

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

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
31
ответ дан 04.10.2019, 10:25
  • 1
    Этот doesn' t работа. – ColinM 10.09.2011, 14:31
  • 2
    Я думаю, что это делает! Выполните команды выше и затем " $ADDR1 эха... $ADDR2" и я получаю "bla@some.com... john@home.com" вывод – nickjb 07.10.2011, 05:33
  • 3
    Это работало ДЕЙСТВИТЕЛЬНО хорошо на меня... Я использовал его для выполнения итерации по массиву строк, какая содержавшая запятая разделила DB, СЕРВЕР, Данные порта для использования mysqldump. – Nick 29.10.2011, 04:36
  • 4
    Диагноз: IFS=";" присвоение существует только в эти $(...; echo $IN) подоболочка; это - то, почему некоторые читатели (включая меня) первоначально думают он won' t работа. Я предположил, что все $IN становились хлебавшими ADDR1. Но nickjb корректен; это действительно работает. Причина состоит в том, что echo $IN команда анализирует свои аргументы с помощью текущего значения $IFS, но тогда повторяет их к stdout использование разделителя пространства, независимо от установки $IFS. Таким образом, результирующий эффект состоит в том, как будто каждый звонил read ADDR1 ADDR2 <<< "bla@some.com john@home.com" (обратите внимание, что вход разделен пробелом нет; - разделенный). – dubiousjim 31.05.2012, 19:28

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

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

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
ответ дан 04.10.2019, 10:25

Взято из Разделенный массив сценариев оболочки 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;
884
ответ дан 04.10.2019, 10:25
  • 1
    Я просто хочу добавить: это является самым простым из всех, можно получить доступ к элементам массива с $ {arrIN [1]} (начинающий с нулей, конечно) – Oz123 22.03.2011, 06:50
  • 2
    Найденный им: метод изменения переменной в $ {} известен как ' параметр expansion'. – KomodoDave 06.01.2012, 04:13
  • 3
    Это работает, когда исходная строка содержит пробелы? – qbolec 25.02.2013, 22:12
  • 4
    Нет, я don' t думают, что это работает, когда существует также подарок пробелов... it' s преобразование ' ' к ' ' и затем создавая разделенный пробелом массив. – Ethan 13.04.2013, 12:47
  • 5
    Это - плохой подход по другим причинам: Например, если Ваша строка будет содержать ;*;, то эти * будет расширен до списка имен файлов в текущем каталоге.-1 – Charles Duffy 07.07.2013, 04:39

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

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

Посмотрите:

$ in= 

Хитрость для этого заключается в использовании опция -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")
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")
28
ответ дан 04.10.2019, 10:25

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

IFS=';' read ADDR1 ADDR2 <<<$IN
22
ответ дан 04.10.2019, 10:25
  • 1
    Рассмотрите использование read -r ..., чтобы гарантировать что, например, эти два символа " \t" во входе заканчиваются как те же два символа в Ваших переменных (вместо единственного символа вкладки). – dubiousjim 31.05.2012, 19:36
  • 2
    - 1 Это не работает здесь (человечность 12.04). Добавление echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2" к Вашему отрывку произведет ADDR1 bla@some.com john@home.com\nADDR2 (\n, новая строка), – Luca Borrione 04.09.2012, 00:07

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

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

даст

bla@some.com john@home.com

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

51
ответ дан 04.10.2019, 10:25
  • 1
    Или еще более простой: эхо " bla@some.com; john@home.com" | awk ' НАЧНИТЕ {RS ="; "} {печать} ' – Jaro 08.01.2014, 10:30
  • 2
    @Jaro Это работало отлично на меня, когда я имел строку с запятыми и должен был переформатировать ее в строки. Спасибо. – Aquarelle 07.05.2014, 11:58

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

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

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

вы получите:

b = foo
c = bar
17
ответ дан 04.10.2019, 10:25

Еще один поздний ответ ... Если вы склонны к Java, вот решение bashj ( https://sourceforge.net/projects/bashj/ ):

#!/usr/bin/bashj

#!java

private static String[] cuts;
private static int cnt=0;
public static void split(String words,String regexp) {cuts=words.split(regexp);}
public static String next() {return(cnt<cuts.length ? cuts[cnt++] : "null");}

#!bash

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

: j.split($IN,";")    # java method call

while true
do
    NAME=j.next()     # java method call
    if [ $NAME != null ] ; then echo $NAME ; else exit ; fi
done
-5
ответ дан 04.10.2019, 10:25

Теги

Похожие вопросы