У меня есть эта строка, хранящаяся в переменной:
IN="[email protected];[email protected]"
Теперь я хотел бы разделить строки на разделитель ;
, чтобы у меня было:
ADDR1="[email protected]"
ADDR2="[email protected]"
Мне необязательно нужны переменные ADDR1
и ADDR2
. Если они являются элементами массива, которые еще лучше.
После предложений из нижеприведенных ответов я закончил следующее, что было после:
#!/usr/bin/env bash
IN="[email protected];[email protected]"
mails=$(echo $IN | tr ";" "\n")
for addr in $mails
do
echo "> [$addr]"
done
Вывод:
> [[email protected]]
> [[email protected]]
Было решение, связанное с установкой Internal_field_separator (IFS) на ;
. Я не уверен, что случилось с этим ответом, как вы reset IFS
вернулись к умолчанию?
RE: IFS
solution, я пробовал это, и он работает, я сохраняю старый IFS
, а затем восстанавливаю его:
IN="[email protected];[email protected]"
OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
echo "> [$x]"
done
IFS=$OIFS
Кстати, когда я пробовал
mails2=($IN)
У меня появилась первая строка при печати в цикле, без скобок вокруг $IN
она работает.
Вы можете установить переменную internal field separator (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"
Взято из Bash shell script split array:
IN="[email protected];[email protected]"
arrIN=(${IN//;/ })
Пояснение:
Эта конструкция заменяет все вхождения ';'
(начальная //
означает глобальную замену) в строке IN
с помощью ' '
(одно пробел), а затем интерпретирует строку с разделителями пробела как массив (это что окружающие круглые скобки).
Синтаксис, используемый внутри фигурных скобок для замены каждого символа ';'
символом ' '
, называется Расширение параметров.
Есть некоторые распространенные ошибки:
Если вы не возражаете обрабатывать их немедленно, мне нравится делать это:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
Вы можете использовать этот тип цикла для инициализации массива, но, вероятно, это более простой способ сделать это. Надеюсь, что это поможет.
В этом вопросе SO уже есть много другого способа сделать это в bash. Но bash имеет много специальных функций, так называемых bashism, которые работают хорошо, но это не будет работать ни в одном другом shell.
В частности, массивы, ассоциативный массив и подстановка шаблонов являются чистыми bashisms и могут не работать под другими оболочками.
В моем Debian GNU/Linux есть стандартная оболочка под названием dash, но я знаю многих людей, которые любят использовать ksh.
Наконец, в очень маленькой ситуации есть специальный инструмент busybox со своим интерпретатором оболочки (ash).
Образец строки в вопросе SO:
IN="[email protected];[email protected]"
Поскольку это может быть полезно с пробелами, и поскольку пробелы могут изменять результат подпрограммы, я предпочитаю использовать эту примерную строку:
IN="[email protected];[email protected];Full Name <[email protected]>"
При чистом bash мы можем использовать массивы и IFS:
var="[email protected];[email protected];Full Name <[email protected]>"
oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS
IFS=\; read -a fields <<<"$var"
Использование этого синтаксиса в недавнем bash не меняет $IFS
для текущего сеанса, но только для текущей команды:
set | grep ^IFS=
IFS=$' \t\n'
Теперь строка var
разделяется и сохраняется в массиве (с именем fields
):
set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'
Мы можем запросить переменное содержимое с помощью declare -p
:
declare -p var fields
declare -- var="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
read
- самый быстрый способ выполнить разделение, потому что не существует вил и внешних ресурсов.
Оттуда вы можете использовать синтаксис, который вы уже знаете для обработки каждого поля:
for x in "${fields[@]}";do
echo "> [$x]"
done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
или отбросить каждое поле после обработки (мне нравится этот подход переключения):
while [ "$fields" ] ;do
echo "> [$fields]"
fields=("${fields[@]:1}")
done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
или даже для простой распечатки (более короткий синтаксис):
printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
Но если вы напишете что-нибудь, пригодное для использования во многих оболочках, вы должны использовать не бахизмы.
Существует синтаксис, используемый во многих оболочках, для разделения строки на первое или последнее вхождение подстроки:
${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:
#
и%
удалите кратчайшую совпадающую строку и
##
и%%
удалить максимально возможное время.
Этот небольшой образец script работает хорошо под bash, dash, ksh, busybox и также был протестирован в Mac OS bash:
var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
iter=${var%%;*}
echo "> [$iter]"
[ "$var" = "$iter" ] && \
var='' || \
var="${var#*;}"
done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
Удачи!
#
, ##
, %
и %%
имеют то, что IMO проще запомнить (насколько они удаляются): #
и %
удаляют самую короткую подходящую строку, а ##
и %%
удаляют самую длинную из возможных.
IFS=\; read -a fields <<<"$var"
завершается с ошибкой на новых строках и добавляет завершающий перевод на новую строку. Другое решение удаляет завершающее пустое поле.
Я видел пару ответов, ссылающихся на команду cut
, но все они были удалены. Немного странно, что об этом никто не говорил, потому что я считаю это одной из наиболее полезных команд для этого типа вещей, особенно для разбора файлов журналов с разделителями.
В случае разделения этого конкретного примера на массив bash script tr
, вероятно, более эффективен, но cut
может быть использован и более эффективен, если вы хотите вытащить определенные поля из средний.
Пример:
$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]
Вы можете, очевидно, поместить это в цикл и перебрать параметр -f, чтобы вытащить каждое поле независимо.
Это становится более полезным, если у вас есть файл журнала с разделителями с такими строками:
2015-04-27|12345|some action|an attribute|meta data
cut
очень удобно, чтобы иметь возможность cat
этого файла и выбрать конкретное поле для дальнейшей обработки.
cut
, это правильный инструмент для работы! Очищено намного больше, чем любой из этих взломов.
Как насчет этого подхода:
IN="[email protected];[email protected]"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}"
IFS";" && Array=($IN)
Это сработало для меня:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
echo "[email protected];[email protected]" | sed -e 's/;/\n/g'
[email protected]
[email protected]
IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )
создаст массив из 8 элементов в этом case (элемент для каждого слова разделен пробелом), а не 2 (элемент для каждой строки разделен точкой с запятой)
Это также работает:
IN="[email protected];[email protected]"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`
Будьте осторожны, это решение не всегда правильно. Если вы передадите только "[email protected]", он назначит его как ADD1, так и ADD2.
Я думаю, AWK - лучшая и эффективная команда для решения вашей проблемы. AWK включен в Bash по умолчанию почти в каждом дистрибутиве Linux.
echo "[email protected];[email protected]" | awk -F';' '{print $1,$2}'
даст
[email protected] [email protected]
Конечно, вы можете хранить каждый адрес электронной почты, переопределяя поле печати awk.
Другой подход к Darron answer, вот как я это делаю:
IN="[email protected];[email protected]"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
Это должно работать везде:
echo "luke;yoda;leila" | tr ";" "\n"
(Обратите внимание, что этот метод стоит того, если вы новичок в Bash, и вам просто нужен простой и короткий трюк. Академический и "правильный" способ заключается в использовании IFS, как указано в других сообщениях.)
В 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 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
(aka readarray
) поддерживает параметр -d
для указания разделителя. Следовательно, другой канонический способ:
mapfile -d ';' -t array < <(printf '%s;' "$in")
\n
, пробелами и *
одновременно. Также нет петель; Переменная массива доступна в оболочке после выполнения (в отличие от ответа с наибольшим количеством голосов). Обратите внимание, что in=$'...'
он не работает с двойными кавычками. Я думаю, что нужно больше голосов.
Как насчет этого одного лайнера, если вы не используете массивы:
IFS=';' read ADDR1 ADDR2 <<<$IN
read -r ...
чтобы, например, два символа "\ t" во входных данных оказались одинаковыми двумя символами в ваших переменных (вместо одного символа табуляции).
echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"
к вашему фрагменту приведет к выводу ADDR1 [email protected] [email protected]\nADDR2
(\ n - ADDR1 [email protected] [email protected]\nADDR2
)
Без настройки IFS
Если у вас есть только один двоеточие, вы можете это сделать:
a="foo:bar"
b=${a%:*}
c=${a##*:}
вы получите:
b = foo
c = bar
Вот чистый 3-лайнер:
in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done
где IFS
разграничивает слова на основе разделителя, а ()
используется для создания array. Затем [@]
используется для возврата каждого элемента в виде отдельного слова.
Если после этого у вас есть какой-либо код, вам также необходимо восстановить $IFS
, например. unset IFS
.
$in
кавычках позволяет расширять символы подстановки.
Существует простой и понятный способ:
echo "add:sfff" | xargs -d: -i echo {}
Но вы должны использовать gnu xargs, BSD xargs can not support -d delim. Если вы используете яблочный mac, как я. Вы можете установить gnu xargs:
brew install findutils
затем
echo "add:sfff" | gxargs -d: -i echo {}
Следующая функция 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 и -a
в zsh.
При желании функцию можно поместить в script следующим образом:
#!/usr/bin/env bash
split() {
# ...
}
split "$@"
Это самый простой способ сделать это.
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
вы можете применить awk для многих ситуаций
echo "[email protected];[email protected]"|awk -F';' '{printf "%s\n%s\n", $1, $2}'
также вы можете использовать этот
echo "[email protected];[email protected]"|awk -F';' '{print $1,$2}' OFS="\n"
IN="[email protected];[email protected]"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
echo $entry
done
Выход
[email protected]
[email protected]
Система: Ubuntu 12.04.1
read
здесь и, следовательно, он может расстроить остальную часть кода, если таковой имеется.
Если нет места, почему бы не это?
IN="[email protected];[email protected]"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
Здесь есть несколько интересных ответов (ошибочный вариант), но для чего-то аналогичного расколу на других языках - вот что я понял в исходном вопросе - я решил:
IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";
Теперь ${a[0]}
, ${a[1]}
и т.д., как и следовало ожидать. Используйте ${#a[*]}
для количества терминов. Или, конечно же, повторить:
for i in ${a[*]}; do echo $i; done
ВАЖНОЕ ПРИМЕЧАНИЕ:
Это работает в тех случаях, когда нет проблем, о которых можно было бы беспокоиться, что решило мою проблему, но может не решить вашу проблему. Пойдите с решением $IFS
в этом случае.
IN
содержит более двух адресов электронной почты. Пожалуйста, обратитесь к той же идее (но исправлено) в ответе Палиндрома
${IN//;/ }
(двойная косая черта), чтобы он также работал с более чем двумя значениями. Помните, что любой подстановочный знак ( *?[
) Будет раскрыт. И конечное пустое поле будет отброшено.
Хорошо, ребята!
Вот мой ответ!
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
/etc/os-release
и /etc/lsb-release
предназначены для получения, а не для анализа. Так что твой метод действительно неправильный. Более того, вы не совсем отвечаете на вопрос о том, как разбить строку на разделитель.
В оболочке 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
где //
означает глобальную замену.
Помимо фантастических ответов, которые уже были предоставлены, если это просто вопрос распечатки данных, вы можете использовать awk
:
awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
Это устанавливает разделитель полей в ;
, чтобы он мог проходить через поля с контуром for
и печатать соответственно.
$ IN="[email protected];[email protected]"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [[email protected]]
> [[email protected]]
С другим вводом:
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c d;e_;f"
> [a]
> [b]
> [c d]
> [e_]
> [f]
Две альтернативы bourne-ish, где не требуется bash массивы:
Случай 1: держите его красивым и простым: используйте NewLine в качестве разделителя записей... например.
IN="[email protected]
[email protected]"
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="[email protected];[email protected]"
IN="$(conv_IRS ";" <<< "$IN")"
while read i; do
# process "$i" ... eg.
echo -n "[email:$i]$ORS"
done <<< "$IN"
В обоих случаях суб-список может быть составлен в цикле, является постоянным после завершения цикла. Это полезно при манипулировании списками в памяти, вместо этого хранения списков в файлах. {Приписка сохраняйте спокойствие и продолжайте B-)}
Используйте встроенный set
для загрузки массива $@
:
IN="[email protected];[email protected]"
IFS=';'; set $IN; IFS=$' \t\n'
Затем начнем вечеринку:
echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
set -- $IN
чтобы избежать некоторых проблем с «$ IN», начинающимся с тире. Тем не менее, расширение без кавычек $IN
будет расширять символы подстановки ( *?[
).
Еще один поздний ответ... Если вы настроены на 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="[email protected];[email protected]"
: 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
Возможно, это не самое элегантное решение, но работает с *
и пробелами:
IN="bla@so me.com;*;[email protected]"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
echo "> [`echo $IN | cut -d';' -f$i`]"
done
Выходы
> [bla@so me.com]
> [*]
> [[email protected]]
Другой пример (разделители в начале и в конце):
IN=";bla@so me.com;*;[email protected];"
> []
> [bla@so me.com]
> [*]
> [[email protected]]
> []
В основном он удаляет каждый символ, отличный от ;
, делая delims
, например. ;;;
. Затем он выполняет цикл for
от 1
до number-of-delimiters
, как подсчитано ${#delims}
. Последним шагом является безопасное получение $i
-й части с помощью cut
.
IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? 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
Вывод:
[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)
Объяснение: Простое назначение с помощью скобки() преобразует разделенный точкой с запятой список в массив, если у вас есть правильный IFS при этом. Стандартный цикл FOR обрабатывает отдельные элементы в этом массиве, как обычно. Обратите внимание, что список, указанный для переменной IN, должен быть "жестким", т.е. С одиночными тиками.
IFS необходимо сохранить и восстановить, так как Bash не относится к назначению так же, как и к команде. Альтернативное обходное решение состоит в том, чтобы обернуть назначение внутри функции и вызвать эту функцию с измененным IFS. В этом случае отдельное сохранение/восстановление IFS не требуется. Спасибо за "Bize" за указание на это.
!"#$%&/()[]{}*? are no problem
ну ... не совсем: []*?
являются символами глобуса. Так что насчет создания этого каталога и файла:` mkdir '! "# $% & '; коснитесь '! "# $% & / () [] {} получил хахахаха - не проблема» и запустил команду? простой может быть прекрасным, но когда он сломан, он сломан.
Однострочный разделитель строки, разделенной символом ';' в массив:
IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
Это только устанавливает IFS в подоболочку, поэтому вам не нужно беспокоиться о сохранении и восстановлении его значения.
0: [email protected];[email protected]\n 1:
(\ n - новая строка)
Вы также можете:
dirList=(
some
list
of
elements
)
for i in ${dirList[@]}; do
...
done
Существует два простых метода:
cat "text1;text2;text3" | tr " " "\n"
и
cat "text1;text2;text3" | sed -e 's/ /\n/g'
local IFS=...
где это возможно; (b) -1 дляunset IFS
, это точно не сбрасывает IFS к его значению по умолчанию, хотя я считаю, что неустановленный IFS ведет себя так же, как и значение по умолчанию IFS ($ '\ t \ n'), однако это кажется плохим практикуйте слепое предположение, что ваш код никогда не будет вызываться с IFS, установленным в произвольное значение; (c) другая идея состоит в том, чтобы вызвать подоболочку:(IFS=$custom; ...)
при выходе из подоболочки IFS вернется к тому, что было изначально.