Как разбить строку на разделителе в 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]
Было решение, включающее установку http://en.wikipedia.org/wiki/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
оно работает.
40 ответов
Вы можете установить переменную внутреннего разделителя полей (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:
IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
Объяснение:
Эта конструкция заменяет все вхождения ';'
(начальный //
означает глобальную замену) в строке IN
с ' '
(один пробел), затем интерпретирует строку, разделенную пробелом, как массив (это то, что делают окружающие скобки).
Синтаксис, используемый внутри фигурных скобок для замены каждого ';'
персонаж с ' '
символ называется расширением параметра.
Есть несколько распространенных ошибок:
Я видел пару ответов, касающихся 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
этот файл и выберите конкретное поле для дальнейшей обработки.
Если вы не против обработать их немедленно, мне нравится делать это:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
Вы можете использовать этот тип цикла для инициализации массива, но, вероятно, есть более простой способ сделать это. Надеюсь, это поможет, хотя.
Совместимый ответ
К этому такому вопросу, в bash уже есть много разных способов сделать это. Но у bash есть много специальных функций, так называемый bashism, которые хорошо работают, но не работают в любой другой оболочке.
В частности, массивы, ассоциативные массивы и подстановки шаблонов - это чистые ошибки и могут не работать под другими оболочками.
В моем Debian GNU/Linux есть стандартная оболочка, которая называется dash, но я знаю многих людей, которые любят использовать ksh.
Наконец, в очень маленькой ситуации есть специальный инструмент busybox с собственным интерпретатором оболочки ( ash).
Запрашиваемая строка
Пример строки в SO вопросе:
IN="bla@some.com;john@home.com"
Так как это может быть полезно с пробелами и поскольку пробелы могут изменить результат процедуры, я предпочитаю использовать следующую строку:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Разделить строку на основе разделителя в bash (версия>=4.2)
Под чистым 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>]
или отбросить каждое поле после обработки (мне нравится этот подход смещения):
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>]
Обновление: недавний bash > = 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
(Nota: \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:
#
а также%
удалить максимально короткую подходящую строку и
##
а также%%
удалить максимально долго.где
#
а также##
означает слева (начало) строки и
%
а также%%
означает справа (конец) строки.
Этот небольшой пример скрипта хорошо работает под bash, dash, ksh, busybox и также был протестирован под bash 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>]
Повеселись!
Это сработало для меня:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
Я думаю, что AWK - лучшая и эффективная команда для решения вашей проблемы. AWK включен в Bash по умолчанию почти во всех дистрибутивах Linux.
echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'
дам
bla@some.com john@home.com
Конечно, вы можете сохранить каждый адрес электронной почты, переопределив поле печати awk.
Как насчет этого подхода:
IN="bla@some.com;john@home.com"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}"
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
Это также работает:
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.
Другой ответ на ответ Даррона, вот как я это делаю:
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
Как насчет этого одного лайнера, если вы не используете массивы:
IFS=';' read ADDR1 ADDR2 <<<$IN
В 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")
Без настройки 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
разграничить слова на основе разделителя и ()
используется для создания массива. затем [@]
используется для возврата каждого элемента в виде отдельного слова.
Если у вас есть какой-либо код после этого, вам также необходимо восстановить $IFS
например, unset IFS
,
Следующая функция 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
в Баш и-A
в зш.
При желании функция может быть помещена в скрипт следующим образом:
#!/usr/bin/env bash
split() {
# ...
}
split "$@"
Есть простой и умный способ, как это:
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 {}
Вы можете применить awk во многих ситуациях
echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'
также вы можете использовать это
echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"
Так много ответов и так много сложностей. Попробуйте более простое решение:
echo "string1, string2" | tr , "\n"
(чтение, перевод) заменяет первый аргумент вторым аргументом во входных данных.
Такtr
, "\n" замените запятую символом новой строки во входных данных, и он станет таким:
string1
string2
Здесь есть несколько классных ответов (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="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
Простой ответ:
IN="bla@some.com;john@home.com"
IFS=';' read ADDR1 ADDR2 <<< "${IN}"
Пример вывода:
echo "${ADDR1}" # prints "bla@some.com"
echo "${ADDR2}" # prints "john@home.com"
Это самый простой способ сделать это.
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
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
Помимо фантастических ответов, которые уже были предоставлены, если это просто вопрос распечатки данных, которые вы можете использовать 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]
Ладно, ребята!
Вот мой ответ!
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
Использовать 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
В оболочке 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
где //
означает глобальную замену.
Две альтернативы 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-) }
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 не требуется. Спасибо за "Бизе" за указание на это.