Как разделить строку по разделителю в 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) на ;. Я не уверен, что произошло с тем ответом, как сбросить IFS обратно к значениям по умолчанию?
Касательно решения с 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 это работает.
Разделение строк в Bash
Существует несколько эффективных способов разделения строки по разделителю в Bash, каждый из которых имеет свои преимущества и варианты использования. Наиболее распространенные подходы включают использование IFS (Internal Field Separator), команды tr, расширения параметров и массивов.
Содержание
- Разделение строк с помощью IFS
- Использование команды
tr - Методы расширения параметров
- Подходы на основе массивов
- Полные примеры и лучшие практики
- Обработка крайних случаев
- Рассмотрения производительности
Разделение строк с помощью IFS
Подход с использованием IFS является фундаментальным методом в Bash для разделения строк на основе разделителей полей. Как вы обнаружили, необходимо сохранить и восстановить исходное значение IFS, чтобы избежать влияния на другие части вашего скрипта.
IN="bla@some.com;john@home.com"
# Сохраняем исходный IFS
OIFS=$IFS
# Устанавливаем IFS в качестве разделителя
IFS=';'
# Разделяем на массив
mails=($IN)
# Восстанавливаем исходный IFS
IFS=$OIFS
# Теперь обращаемся к отдельным элементам
echo "Первый email: ${mails[0]}" # Вывод: bla@some.com
echo "Второй email: ${mails[1]}" # Вывод: john@home.com
Ключевая идея здесь заключается в том, что при использовании array=($variable) Bash автоматически разделяет содержимое переменной с использованием текущих символов IFS. Это создает массив, где каждый элемент соответствует подстроке, разделенной разделителем.
Важно: Всегда восстанавливайте исходный IFS после его изменения, так как IFS влияет на многие другие операции оболочки, включая подстановку команд и разделение слов.
Использование команды tr
Ваш подход с использованием tr является еще одним отличным методом, который особенно полезен, когда вы хотите немедленно обработать результаты:
IN="bla@some.com;john@home.com"
# Заменяем разделитель на символы новой строки и обрабатываем
echo "$IN" | tr ";" "\n" | while read -r email; do
echo "> [$email]"
done
Этот метод имеет преимущество в том, что он не изменяет настройку IFS оболочки, что делает его более безопасным для использования в сложных скриптах. Команда tr преобразует каждый символ точки с запятой в символ новой строки, эффективно создавая отдельные строки, которые можно обрабатывать по одной.
Разница между echo $IN и echo "$IN" здесь важна:
echo $INудаляет начальные/конечные пробелы и рассматривает несколько пробелов как одинecho "$IN"сохраняет точное содержимое строки
Методы расширения параметров
Расширение параметров в Bash предоставляет мощные способы манипуляции строками без использования внешних команд:
IN="bla@some.com;john@home.com"
# Использование команды read с IFS
IFS=';' read -ra ADDR <<< "$IN"
echo "Первый: ${ADDR[0]}" # bla@some.com
echo "Второй: ${ADDR[1]}" # john@home.com
Этот метод использует оператор here-string <<< и флаг -a с командой read для автоматического заполнения массива. IFS применяется только к этой операции, поэтому нет необходимости восстанавливать его afterward.
Подходы на основе массивов
Массивы Bash обеспечивают наиболее гибкий способ обработки разделенных строк:
IN="bla@some.com;john@home.com"
# Метод 1: Использование readarray (Bash 4+)
IFS=';' readarray -t ADDRS <<< "$IN"
# Метод 2: Использование mapfile (альтернатива для Bash 4+)
IFS=';' mapfile -t ADDRS <<< "$IN"
# Метод 3: Ваш исходный подход
mails=($IN)
# Обращение к элементам массива
for ((i=0; i<${#ADDRS[@]}; i++)); do
echo "Элемент $i: ${ADDRS[i]}"
done
Синтаксис ${#ADDRS[@]} дает вам длину массива, и вы можете легко перебирать все элементы.
Полные примеры и лучшие практики
Вот комплексный пример, демонстрирующий различные подходы:
#!/usr/bin/env bash
IN="bla@some.com;john@home.com;alice@work.com"
echo "=== Метод 1: IFS с массивом (рекомендуется) ==="
IFS=';' read -ra emails <<< "$IN"
for email in "${emails[@]}"; do
echo "> [$email]"
done
echo -e "\n=== Метод 2: Ваш подход с tr ==="
echo "$IN" | tr ";" "\n" | while read -r email; do
echo "> [$email]"
done
echo -e "\n=== Метод 3: Расширение параметров с read ==="
while IFS=';' read -r email; do
echo "> [$email]"
done <<< "$IN"
echo -e "\n=== Метод 4: Ваш исходный метод IFS (с восстановлением) ==="
OIFS=$IFS
IFS=';'
for email in $IN; do
echo "> [$email]"
done
IFS=$OIFS
Вывод:
=== Метод 1: IFS с массивом (рекомендуется) ===
> [bla@some.com]
> [john@home.com]
> [alice@work.com]
=== Метод 2: Ваш подход с tr ===
> [bla@some.com]
> [john@home.com]
> [alice@work.com]
=== Метод 3: Расширение параметров с read ===
> [bla@some.com]
> [john@home.com]
> [alice@work.com]
=== Метод 4: Ваш исходный метод IFS (с восстановлением) ===
> [bla@some.com]
> [john@home.com]
> [alice@work.com]
Обработка крайних случаев
При разделении строк следует учитывать несколько крайних случаев:
1. Пустые элементы:
IN="a;;b" # Обратите внимание на двойную точку с запятой
IFS=';' read -ra parts <<< "$IN"
echo "Количество частей: ${#parts[@]}" # Вывод: 3 (включая пустую среднюю)
2. Начальные/конечные разделители:
IN=";a;b;" # Начальные и конечные разделители
IFS=';' read -ra parts <<< "$IN"
# Результат - пустые первый и последний элементы
3. Обработка пробелов:
IN="a;b ; c" # Пробел после разделителя
IFS=';' read -ra parts <<< "$IN"
echo "Части: ${parts[@]}" # Обратите внимание на пробел во второй части
Для правильной обработки пробелов можно использовать:
IN="a;b ; c"
IFS=';' read -ra parts <<< "$IN"
# Обрезаем пробелы из каждого элемента
for i in "${!parts[@]}"; do
parts[i]="$(echo "${parts[i]}" | xargs)"
done
4. Специальные символы в разделителях:
Если ваш разделитель содержит специальные символы регулярных выражений, может потребоваться их экранирование:
IN="file1.txt|file2.txt|file3.txt"
IFS='|' read -ra files <<< "$IN" # Экранирование не требуется для простых случаев
Рассмотрения производительности
Для приложений, чувствительных к производительности, учтите следующие факторы:
- Скорость метода: Операции с массивами обычно быстрее, чем внешние команды, такие как
tr - Использование памяти: Большие строки с множеством разделителей могут потреблять значительное количество памяти
- Версия Bash: Некоторые методы требуют Bash 4.0+
Вот быстрое сравнение производительности:
# Большая тестовая строка
IN=$(printf "email%d@domain.com;" {1..10000})
IN=${IN%;} # Удаляем конечный разделитель
# Замеряем время каждого метода
time IFS=';' read -ra emails <<< "$IN"
time echo "$IN" | tr ";" "\n"
time for email in $(echo "$IN" | tr ";" "\n"); do :; done
В целом, метод с массивом IFS является наиболее эффективным для большинства случаев использования, в то время как метод tr более читаем и не требует манипуляции с IFS.
Заключение
- В большинстве случаев используйте
IFS=';' read -ra array <<< "$string", так как он чистый, эффективный и не требует восстановления IFS - Для совместимости со старыми оболочками подход с
trявляется хорошей альтернативой - Всегда восстанавливайте IFS, когда вы временно его изменяете, чтобы избежать влияния на другие операции оболочки
- Обрабатывайте крайние случаи, такие как пустые элементы и пробелы, соответствующим образом
- Учитывайте производительность при работе с очень большими строками или частыми операциями
Метод, который вы обнаружили с сохранением и восстановлением IFS, полностью корректен, но современный подход с использованием read с here-strings обычно предпочтительнее, так как он ограничивает изменение IFS одной операцией.