Как разбить строку на разделитель в 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
по умолчанию?
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
это работает.
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"
принято от bash Shell script split array:
IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
объяснение:
эта конструкция заменяет все вхождения ';'
(первоначальный //
означает глобальную замену) в строке IN
С ' '
(один пробел), затем интерпретирует строку с разделителями пробелов как массив (это то, что делают окружающие круглые скобки).
синтаксис, используемый внутри фигурных скобок для замены каждого ';'
персонаж с ' '
персонаж называется Расширение Параметр.
есть некоторые общие gotchas:
Если вы не возражаете немедленно обработать их, мне нравится делать это:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
вы можете использовать такой цикл для инициализации массива, но, вероятно, есть более простой способ сделать это. Надеюсь, это поможет.
совместимый ответа
к этому вопросу SO, уже есть много разных способов сделать это в Баш. Но у Баша их много!--27-->специальные функции, так называемый bashism это хорошо работает, но это не будет работать в любом другом shell.
в частности, массивы, ассоциативный массив и замена шаблона чисты bashisms и не может работать под другим снаряды.
на Debian GNU/Linux, есть стандартный раковина тире, но я знаю многих людей, которые хотели бы использовать КШ.
наконец, в очень маленькой ситуации есть специальный инструмент под названием busybox и со своим собственным интерпретатором оболочки (Ясень).
просил строка
образец строки в SO вопрос:
IN="bla@some.com;john@home.com"
как это может быть полезно с пробел и пробел может изменить результат процедуры, я предпочитаю использовать эту строку образца:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
разделить строку на основе разделителя в Баш (версия >=4.2)
под чисто Баш, мы можем использовать массивы и ИФС:
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 <<<"$var"
использование этого синтаксиса в недавнем 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 var fields
declare -- var="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
- это quickiest способ сделать сплит, потому что нет вилки и никаких внешних ресурсов называется.
оттуда, вы можете использовать синтаксис, который вы уже знаете, для обработки каждого поля:
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>]
разделить строку на основе разделителя в shell
но если вы напишете что-то полезное под многими оболочками, вы должны не использовать 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:
#
и%
удалить кратчайшую строку соответствия и
##
и%%
удалить как можно дольше.
этот маленький пример сценария хорошо работает под Баш, тире, КШ, 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>]
удачи!
Я видел пару ответов, ссылающихся на 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]}"
это сработало для меня:
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"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`
будьте осторожны, это решение не всегда правильное. На случай, если вы пройдете .bla@some.com " только он назначит его как ADD1, так и ADD2.
Я думаю AWK является лучшей и эффективной командой для решения вашей проблемы. AWK включен в Bash по умолчанию почти в каждом дистрибутиве Linux.
echo "bla@some.com;john@home.com" | awk -F';' '{print ,}'
даст
bla@some.com john@home.com
конечно, вы можете сохранить каждый адрес электронной почты, переопределив поле печати awk.
на Даррон это, вот как я делаю это:
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
в Bash, пуленепробиваемый способ, который будет работать, даже если ваша переменная содержит новые строки:
IFS=';' read -d '' -ra array < <(printf '%s;' "$in")
посмотреть:
$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'
трюк для этого, чтобы работать, чтобы использовать на read
(разделитель) с пустым разделителем, так что read
вынужден читать все, что его кормят. И мы кормимся!--5--> С точно содержанием переменной in
, не пустую строку спасибо printf
. Обратите внимание, что мы также помещаем разделитель в printf
в убедитесь, что строка передана в read
имеет конечный разделитель. Без него,read
будет обрезать потенциальные конечные пустые поля:
$ in='one;two;three;' # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;' "$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")
вот чистый 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
.
без установки IFS
Если у вас есть только одна двоеточие, вы можете сделать это:
a="foo:bar"
b=${a%:*}
c=${a##*:}
вы получите:
b = foo
c = bar
существует простой и умный способ, как это:
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 {}
следующая функция Bash/zsh разбивает свой первый аргумент на разделитель, заданный вторым аргументом:
split() {
local string=""
local delimiter=""
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
в zsh.
при желании, функцию можно поместить в скрипт следующим образом:
#!/usr/bin/env bash
split() {
# ...
}
split "$@"
Это самый простой способ сделать это.
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 ко многим ситуациям
echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", , }'
также вы можете использовать этот
echo "bla@some.com;john@home.com"|awk -F';' '{print ,}' OFS="\n"
если нет места, почему бы не этот?
IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
здесь есть несколько интересных ответов (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
раствора(s) в этом случае.
использовать set
встроенный для загрузки $@
время:
IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'
тогда пусть вечеринка начнется:
echo $#
for a; do echo $a; done
ADDR1= ADDR2=
две альтернативы bourne-ish, где ни один не требует массивов bash:
корпус 1: держите его красивым и простым: используйте новую строку в качестве разделителя записей... например.
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 "" "$NL"
}
conv_ORS() {
exec tr "$NL" ""
}
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-) }
помимо фантастических ответов, которые уже были предоставлены, если речь идет только о распечатке данных, которые вы можете использовать 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]
в оболочке 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
здесь //
означает глобальную замену.
Ладно, ребята!
вот мой ответ!
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
однострочный разделитель строки, разделенной на';', в массив:
IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
Это только устанавливает IFS в подрешетке, поэтому вам не нужно беспокоиться о сохранении и восстановлении его значения.
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 при этом. Стандарт для цикла обрабатывает отдельные элементы в этом массиве, как обычно. Обратите внимание, что список, указанный для переменной IN, должен быть "жестким", то есть с одиночными тиками.
IFS необходимо сохранить и восстановить, так как Bash не обрабатывает назначение так же, как команду. Заместитель обходной путь-обернуть назначение внутри функции и вызвать эту функцию с измененным IFS. В этом случае отдельное сохранение/восстановление IFS не требуется. Спасибо за "Bize" за указание на это.
может быть, не самое элегантное решение, но работает с *
и пробелы:
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
to number-of-delimiters
как сосчитать ${#delims}
. Последний шаг-безопасно получить $i
й части, используя cut
.