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

  1. если исходная строка имеет пробелы, вам нужно будет использовать ИФС:
    • IFS=':'; arrIN=($IN); unset IFS;
  2. если исходная строка содержит пробелы и разделитель-новая строка, вы можете установить ИФС С:
    • IFS=$'\n'; arrIN=($IN); unset IFS;

Если вы не возражаете немедленно обработать их, мне нравится делать это:

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

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.


Я думаю 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")

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

IFS=';' read ADDR1 ADDR2 <<<$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

почему этот подход "лучший" для меня?

по двум причинам:

  1. ты не нужно бежать разделитель;
  2. не будет с пробелами. Значение будет правильно разделено в массиве!

[] ' 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.