Поиск подстрок в Bash: простые и надёжные методы

Быстрые ссылки
Зачем это полезно?
Поиск подстроки с встроенными средствами Bash
Поиск подстроки с grep
Поиск подстроки с sed
Остановить поиск — найдено
Зачем это полезно?
Частая задача в скриптах — проверить, содержится ли в строке более короткая подстрока. Например, парсинг конфигурационных файлов, анализ строки ввода от пользователя, проверка меток устройств и т. п.
В Linux есть много инструментов для работы с текстом: некоторые функциональности встроены в shell, другие реализованы отдельными программами. Для простых проверок лучше применять минимально необходимое средство: это уменьшает зависимость от внешних утилит и повышает читаемость.
Некоторые файлы в системе — не обычные файлы, а интерфейсы к данным (например, /proc или /sys). Входные и выходные данные команд в терминале тоже — текст, что позволяет направлять, фильтровать и комбинировать вывод через каналы (pipe) и перенаправления.
Ниже — набор простых, надёжных приёмов, которые удобно использовать в своих скриптах.
Поиск подстроки с встроенными средствами Bash
Встроенный тест в двойных скобках ([[ … ]]) поддерживает шаблоны (globbing). Самый распространённый приём — обернуть подстроку звёздочками:
if [[ "monkey" = *"key"* ]]; then
echo "key is in monkey"
else
echo "key is not in monkey"
fiСохраните как double.sh (в исходном примере). Не забудьте сделать файл исполняемым:
chmod +x double.shЗапуск:
./double.shПочему это работает: символ * в шаблоне соответствует любой последовательности символов (включая пустую). Если key встречается внутри строки, весь шаблон совпадёт.
Пример с переменными (double2.sh):
#!/bin/bash
string="Monkey"
substring="key"
if [[ $string = *$substring* ]]; then
echo "$substring was found in $string"
else
echo "$substring was not found in $string"
fiЗапуск как
./double2.shСоветы по надёжности и кавычкам:
- Внутри [[ … ]] нет разбиения на слова (word splitting) и нет расширения пути (pathname expansion) для строк в левой части, но шаблоны справа всё ещё обрабатываются. Часто безопаснее писать
if [[ $string == *"$substring"* ]]; then
...
fiзвёздочки остаются как часть шаблона, а сама переменная подстроки взята в кавычки — это предотвращает неожиданное разбиение при наличии пробелов и сохраняет поведение шаблона.
- Если подстрока может содержать символы шаблона (например
*,?,[), и вы хотите искать именно литеральную последовательность, не используйте необработанный шаблон. Для литерального поиска применяйте grep -F или экранируйте метасимволы (см. разделы ниже).
Функция для переиспользования (double3.sh):
#!/bin/bash
shopt -s nocasematch
string="Monkey"
substring="Key"
capital="London"
check_substring ()
{
if [[ $1 = *$2* ]]; then
echo "$2 was found in $1"
else
echo "$2 was not found in $1"
fi
}
check_substring "Monkey" "key"
check_substring $string $substring
check_substring $string "banana"
check_substring "Wales" $capitalЗапуск:
./double3.sh
Примечание о регистре: shopt -s nocasematch делает сравнения в [[…]] нечувствительными к регистру.
Использование case:
#!/bin/bash
shopt -s nocasematch
string="Wallaby"
substring="Wall"
case $string in
*$substring*)
echo "$substring was found in $string"
;;
*)
echo "Nothing matched: $string"
;;
esaccase особенно удобен, когда нужно сопоставить строку с множеством вариантов. Шаблоны в case читаемы и просты для отладки.
Важно: оператор [[ … ]] является расширением Bash (и некоторых других современных шеллов), минимально совместимый с POSIX sh — не всегда. Если требуется совместимость с /bin/sh на старых системах, используйте portable-подходы (grep/awk) или проверьте доступность Bash.
Советы по безопасности и устойчивости в скриптах:
- Избегайте echo $var без кавычек: используйте printf ‘%s’ “$var” или echo “$var”.
- Если переменные берутся из внешнего ввода, учитывайте возможность инъекций в командные подстановки и аккуратно экранируйте или избегайте их.
Поиск подстроки с grep
grep — стандартный инструмент для поиска. Для простых литеральных совпадений лучше использовать фиксированный режим (-F), а для тихого обнаружения — опцию -q.
Пример subgrep.sh:
#!/bin/bash
string="porridge pot"
substring="ridge"
if $(echo $string | grep -q $substring); then
echo "$substring was found in $string"
else
echo "$substring was not found in $string"
fiУлучшенная и надёжная версия (рекомендуется):
#!/bin/bash
string="porridge pot"
substring="ridge"
if printf '%s' "$string" | grep -Fq -- "$substring"; then
echo "$substring was found in $string"
else
echo "$substring was not found in $string"
fiПояснения:
- printf ‘%s’ “$string” безопаснее, чем echo $string: он не интерпретирует опции и сохраняет пробелы.
- grep -Fq ищет фиксированную строку (-F) и не печатает вывод (-q). Флаг – защищает от случаев, когда подстрока начинается с дефиса (
-). - grep возвращает код выхода: 0 при совпадении, 1 при отсутствии, >1 при ошибке.
Когда использовать grep:
- Если подстрока может содержать regex-метасимволы и вы не хотите их интерпретировать — используйте -F.
- Если нужно использовать регулярные выражения — используйте без -F или с -E для расширенных regex.
Поиск подстроки с sed
sed часто применяется для обработки строк и может помочь при проверке наличия подстроки.
Шаблон, который печатает строки, содержащие подстроку:
"/$substring/p"Пример subsed.sh:
#!/bin/bash
string="Sweden"
substring="eden"
if [ -n "$(sed -n "/$substring/p" <<< $string)" ]; then
echo "$substring was found in $string"
else
echo "$substring was not found in $string"
fiРекомендации и улучшения:
- Используйте кавычки с here-string: <<< “$string” — это сохранит пробелы и специальные символы.
- Если подстрока содержит символ
/, смените разделитель в sed, напримерsed -n "\@$substring@p"(символ@вместо/) или предварительно экранируйте слэши. - sed интерпретирует регулярные выражения; если требуется только литеральный поиск, grep -F может быть проще.
Улучшенная версия с безопасными кавычками и альтернативным разделителем:
#!/bin/bash
string="dir/with/slash"
substring="dir/with"
if [ -n "$(sed -n "\@$substring@p" <<< "$string")" ]; then
echo "$substring was found in $string"
else
echo "$substring was not found in $string"
fiДругие инструменты: awk и Perl
- awk: простая проверка
awk '/pattern/ { found=1 } END { exit !found }'— полезно, если нужно обрабатывать поля в строках. - Perl: для сложных задач с Unicode и экранированием используйте Perl-совместимые механизмы:
perl -ne 'exit 0 if /quotemeta($ARGV[0])/' -- "$substring"— но Perl обычно излишен для одноразовой проверки.
Пример awk-клипа:
if printf '%s' "$string" | awk -v s="$substring" 'index($0,s){exit 0} END{exit 1}'; then
echo "found"
fiКогда встроенные средства не подходят — типичные кейсы
- Подстрока содержит символы шаблона (*, ?, [) — если нужен буквальный поиск, используйте grep -F или заранее экранируйте.
- Работаете в чистом POSIX sh без [[ ]] — используйте grep/awk.
- Нужна поддержка сложных Unicode-символов и нормализации — рассмотрите Perl или утилиты с поддержкой Unicode.
Примеры неочевидных ошибок и как их избежать
- Проблема: переменная содержит пробелы
Неправильно:
if [[ $string = *$substring* ]]; then ...Правильно:
if [[ $string == *"$substring"* ]]; then ...- Проблема: подстрока начинается с дефиса
-
Неправильно передавать как аргумент grep без --.
Исправление:
printf '%s' "$string" | grep -Fq -- "$substring"- Проблема: подстрока содержит
/при использовании sed с/как разделителем
Решение: смените разделитель или экранируйте слэши:
sed -n "\@$substring@p"- Проблема: шаблон интерпретируется как glob при использовании [[ … ]]
Если хотите точное соответствие, избегайте незахищённых шаблонов или используйте grep -F.
Лучшие практики и рекомендации (чек-лист)
Для разработчика скриптов:
- Всегда ставьте кавычки там, где это важно: “$variable”.
- Используйте printf вместо echo для пользовательских строк.
- Предпочитайте grep -Fq для буквального поиска.
- Для простых проверок используйте [[ $string == “$substr” ]] (Bash).
- Если подстрока — пользовательский ввод, фильтруйте спецсимволы или используйте безопасные режимы поиска.
Для системного администратора:
- Проверяйте совместимость со /bin/sh на целевых системах.
- Если скрипт должен быть переносимым, исходите из минимального набора утилит.
- Логируйте ситуации, где совпадение не найдено, чтобы отладить возможные проблемы с кодировкой.
Для SRE / CI:
- Добавьте unit-тесты для граничных случаев (пустая строка, специальные символы, длинные строки).
- Убедитесь, что локаль (LC_ALL/LC_COLLATE) не влияет на поведение поиска.
Критерии приёмки
- Скрипт правильно определяет наличие подстроки в следующих случаях: пустая подстрока, подстрока в начале, в середине, в конце.
- Скрипт корректно обрабатывает пробелы и табуляции внутри строк.
- Скрипт не ломается, если подстрока содержит glob-метасимволы или слэши.
- Скрипт работает на целевой оболочке (Bash или sh) и задокументирована требуемая среда выполнения.
Небольшая методология: как выбрать инструмент
- Нужен ли буквальный поиск? Если да — grep -Fq.
- Нужна ли нечувствительность к регистру? grep -i или shopt -s nocasematch в Bash.
- Требуется ли повышение производительности при большом объёме данных? Для потоковых данных используйте grep/awk; для коротких строк — встроенные тесты быстрее.
- Нужна ли переносимость на /bin/sh? Если да — не используйте [[ … ]].
Ментальные модели / эвристики
- “Если задача простая — используй самое простое средство”: для единичной проверки подстроки — Bash [[…]] или grep -F.
- “Если строка может содержать метасимволы — думай о буквализации”: применяй fixed-mode или экранируй.
- “Если нужна масштабируемость — используй потоковые утилиты”: grep/awk эффективны для больших объёмов.
Примеры тест-кейсов и приёмочные тесты
Тесты, которые стоит автоматизировать:
- Поиск “a” в “a” → найдено
- Поиск “” (пустая) в “anything” → результат зависит от выбранного поведения (обычно считать найденной)
- Подстрока с пробелом: “a b” в “x a b y” → найдено
- Подстрока с метасимволами: ““ в “ab” → если нужен буквальный поиск — считать найденным
- Подстрока со слэшем: “dir/with” в “dir/with/slash” → найдено
Decision flowchart
flowchart TD
A[Нужно проверить подстроку?] --> B{Буквальный поиск?}
B -- Да --> C[Использовать grep -Fq или awk index]
B -- Нет --> D{Требуется regex?}
D -- Да --> E[Использовать grep -E / sed / Perl]
D -- Нет --> F{Совместимость с POSIX?}
F -- Да --> C
F -- Нет --> G[Можно использовать [[ ... ]] или case в Bash]
C --> H[Проверить кавычки и -- для дефисов]
E --> H
G --> HКраткий шпаргалка (cheat sheet)
- Bash (регистр учитывается): [[ $s == “$sub” ]]
- Bash (игнорировать регистр): shopt -s nocasematch
- grep фиксированная строка: printf ‘%s’ “$s” | grep -Fq – “$sub”
- sed: sed -n “\@$sub@p” <<< “$s” (смена разделителя)
- awk: printf ‘%s’ “$s” | awk -v s=”$sub” ‘index($0,s){exit 0} END{exit 1}’
Контрпримеры / когда это ломается
- Некорректно экранированные кавычки приведут к ошибкам.
- Локаль может повлиять на сравнение символов (особенно сортировка/сравнение Unicode).
- Если строка очень большая, вложенные конструкции и дорогие трансформации могут замедлить.
Безопасность и приватность
- Не подставляйте необработанные пользовательские строки напрямую в команды без экранирования, особенно если используете конструкцию вида eval или командную подстановку.
- Для поиска в логах с чувствительной информацией фильтруйте и не выводите секреты в логах.
1‑строчный глоссарий
- Подстрока — непрерывная последовательность символов внутри строки.
- Глоб (glob) — шаблон файловой маски (*, ?, []).
- fixed-mode — режим поиска фиксированной строки (без regex), часто реализуется через grep -F.
Итог
Для большинства повседневных сценариев встроенные возможности Bash ([[…]] и case) или простой grep -Fq обеспечивают достаточную надёжность и читаемость. Если подстрока может содержать специальные символы или требуется совместимость с POSIX sh, отдайте предпочтение grep/awk. Всегда оборачивайте переменные в кавычки и используйте безопасные методы чтения/передачи строк (printf, here-strings с кавычками).
Короткий чек-лист для внедрения в проект
- Выбрана целевая оболочка (Bash vs sh)
- Обработаны пробелы и спецсимволы
- Написаны тесты для граничных случаев
- Документировано поведение при пустой подстроке
Похожие материалы
Несколько аккаунтов Skype: Multi Skype Launcher
Журнал для работы: повысить продуктивность
Персональные звуки уведомлений на Android
Скачивание шоу Hulu для офлайн‑просмотра
Microsoft Start: персонализированная новостная лента