У меня есть эта строка, хранящаяся в переменной:
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
это работает.
Две альтернативы 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-)}
Используйте встроенную функцию 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
Однострочник для разделения строки, разделенной ';' в массив:
IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
Это только устанавливает IFS в подоболочке, так что вам не нужно беспокоиться о сохранении и восстановлении его значения.
0: bla@some.com;john@home.com\n 1:
(\n новая строка),
– Luca Borrione
04.09.2012, 00:04
Здесь есть несколько интересных ответов (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
.
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
этот файл и выбрать определенное поле для дальнейшего использования. обработки.
Как насчет этого подхода:
IN="bla@some.com;john@home.com"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}"
IFS";" && Array=($IN)
– ata
04.11.2011, 11:33
Это сработало для меня:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
Другой взгляд на ответ Даррона , вот как я это делаю:
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
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
Почему этот подход «лучший» для меня?
По двум причинам:
[] 's
Взято из Разделенный массив сценариев оболочки Bash :
IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
Объяснение:
Эта конструкция заменяет все вхождения ';'
( начальная //
означает глобальную замену) в строке IN
на ' '
(один пробел), а затем интерпретирует строку, разделенную пробелом, как массив (это то, что делают окружающие скобки).
Синтаксис, используемый внутри фигурных скобок для замены каждого символа ';'
на символ ' '
, называется Расширение параметра .
Есть несколько распространенных ошибок:
\n'; arrIN=($IN); unset IFS;;*;
, то эти *
будет расширен до списка имен файлов в текущем каталоге.-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, встроенный модуль mapfile
(он же readarray
) поддерживает опцию -d
для указания разделителя. Отсюда другой канонический путь:
mapfile -d ';' -t array < <(printf '%s;' "$in")
Как насчет этого одного лайнера, если вы не используете массивы:
IFS=';' read ADDR1 ADDR2 <<<$IN
read -r ...
, чтобы гарантировать что, например, эти два символа " \t" во входе заканчиваются как те же два символа в Ваших переменных (вместо единственного символа вкладки).
– dubiousjim
31.05.2012, 19:36
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.
Без настройки IFS
Если у вас есть только двоеточие, вы можете сделать это:
a="foo:bar"
b=${a%:*}
c=${a##*:}
вы получите:
b = foo
c = bar
Еще один поздний ответ ... Если вы склонны к 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