Как проверить, содержит ли строка подстроку в Bash

1952

У меня есть строка в Bash:

string="My string"

Как проверить, содержит ли она еще одну строку?

if [ $string ?? 'foo' ]; then
  echo "It there!"
fi

Где ?? - мой неизвестный оператор. Использовать эхо и grep?

if echo "$string" | grep 'foo'; then
  echo "It there!"
fi

Это выглядит немного неуклюжим.

  • 2
    Привет, если пустые строки ложные, почему вы считаете это неуклюжим? Это был единственный способ, который работал для меня, несмотря на предложенные решения.
  • 1
    Вы можете использовать команду expr здесь
Показать ещё 1 комментарий
Теги:
string
substring

22 ответа

2934
Лучший ответ

Вы можете использовать ответ Marcus (* подстановочные знаки) за пределами оператора case, если вы используете двойные скобки:

string='My long string'
if [[ $string == *"My long"* ]]; then
  echo "It there!"
fi

Обратите внимание, что пробелы в игольной веревке нужно размещать между двойными кавычками, а подстановочные знаки * должны быть снаружи.

  • 90
    Также обратите внимание, что вы можете отменить сравнение, просто переключившись на! = В тесте. Спасибо за ответ!
  • 2
    Хм, с этим точным кодом я получаю [[: not found . Есть идеи, что случилось? Я использую GNU Bash, версия 4.1.5 (1), на Ubuntu.
Показать ещё 32 комментария
399

Если вы предпочитаете подход с регулярным выражением:

string='My string';

if [[ $string =~ .*My.* ]]
then
   echo "It there!"
fi
  • 2
    Пришлось заменить регулярное выражение egrep в сценарии Bash, это работало отлично!
  • 0
    Если мне нужно пространство, оно не работает, как это сделать?
Показать ещё 5 комментариев
275

Я не уверен в использовании оператора if, но вы можете получить аналогичный эффект с аргументом case:

case "$string" in 
  *foo*)
    # Do stuff
    ;;
esac
  • 62
    Это, вероятно, лучшее решение, так как оно переносимо для посылок снарядов. (ака нет башизмов)
  • 19
    @technosaurus Я нахожу довольно странным критиковать "bashism" в вопросе, который имеет только тег bash :)
Показать ещё 9 комментариев
167

Совместимый ответ

Как уже было много ответов с использованием Bash -специфических функций, есть способ работать с более белыми признаками, например :

[ -z "${string##*$reqsubstr*}" ]

На практике это может дать:

string='echo "My string"'
for reqsubstr in 'o "M' 'alt' 'str';do
  if [ -z "${string##*$reqsubstr*}" ] ;then
      echo "String '$string' contain substring: '$reqsubstr'."
    else
      echo "String '$string' don't contain substring: '$reqsubstr'."
    fi
  done

Это было протестировано в , , и ash (busybox), и результат всегда:

String 'echo "My string"' contain substring: 'o "M'.
String 'echo "My string"' don't contain substring: 'alt'.
String 'echo "My string"' contain substring: 'str'.

В одну функцию

Как просил @EeroAaltonen вот версия той же демонстрации, протестированная под теми же оболочками:

myfunc() {
    reqsubstr="$1"
    shift
    string="$@"
    if [ -z "${string##*$reqsubstr*}" ] ;then
        echo "String '$string' contain substring: '$reqsubstr'.";
      else
        echo "String '$string' don't contain substring: '$reqsubstr'." 
    fi
}

Тогда:

$ myfunc 'o "M' 'echo "My String"'
String 'echo "My String"' contain substring 'o "M'.

$ myfunc 'alt' 'echo "My String"'
String 'echo "My String"' don't contain substring 'alt'.

Примечание: вам нужно избегать или дублировать кавычки и/или двойные кавычки:

$ myfunc 'o "M' echo "My String"
String 'echo My String' don't contain substring: 'o "M'.

$ myfunc 'o "M' echo \"My String\"
String 'echo "My String"' contain substring: 'o "M'.

Простая функция

Это было протестировано в , и, конечно, :

stringContain() { [ -z "${2##*$1*}" ]; }

Что все люди!

Тогда теперь:

$ if stringContain 'o "M3' 'echo "My String"';then echo yes;else echo no;fi
no
$ if stringContain 'o "M' 'echo "My String"';then echo yes;else echo no;fi
yes

... Или, если поданная строка может быть пуста, как указано в @Sjlver, функция будет выглядеть следующим образом:

stringContain() { [ -z "${2##*$1*}" ] && [ -z "$1" -o -n "$2" ]; }

или, как было предложено комментарием Адриана Гюнтера, избегая -o switche:

stringContain() { [ -z "${2##*$1*}" ] && { [ -z "$1" ] || [ -n "$2" ] ;} ; }

С пустыми строками:

$ if stringContain '' ''; then echo yes; else echo no; fi
yes
$ if stringContain 'o "M' ''; then echo yes; else echo no; fi
no
  • 1
    Это было бы еще лучше, если бы вы могли найти какой-то способ поместить это в функцию.
  • 2
    @EeroAaltonen Как найти мою (новую добавленную) функцию?
Показать ещё 18 комментариев
127

Вы должны помнить, что сценарий оболочки меньше языка и больше набора команд. Интуитивно вы думаете, что этот "язык" требует от вас следовать if с помощью [ или [[. Обе эти команды - это просто команды, которые возвращают статус выхода, указывающий на успех или неудачу (как и любая другая команда). По этой причине я использовал бы grep, а не команду [.

Просто выполните:

if grep -q foo <<<"$string"; then
    echo "It there"
fi

Теперь, когда вы думаете о if как тестировании статуса выхода для следующей команды (в комплекте с точкой с запятой). Почему бы не пересмотреть источник тестируемой строки?

## Instead of this
filetype="$(file -b "$1")"
if grep -q "tar archive" <<<"$filetype"; then
#...

## Simply do this
if file -b "$1" | grep -q "tar archive"; then
#...

Опция -q делает grep ничего не выводить, поскольку нам нужен только код возврата. <<< заставляет оболочку расширять следующее слово и использовать его как вход в команду, однострочную версию документа << (я не уверен, является ли это стандартом или башизмом).

  • 7
    они называются здесь строки (3.6.7) Я считаю, что это bashism
  • 10
    можно также использовать подстановку процесса, if grep -q foo <(echo somefoothing); then
Показать ещё 13 комментариев
75

Принятый ответ лучше, но поскольку есть более чем один способ сделать это, здесь другое решение:

if [ "$string" != "${string/foo/}" ]; then
    echo "It there!"
fi

${var/search/replace} имеет значение $var, если первый экземпляр search заменен на replace, если он найден (он не меняет $var). Если вы попытаетесь заменить foo на ничего, и строка изменилась, то, очевидно, был найден foo.

  • 5
    Решение ephemient выше:> `if [" $ string "! =" $ {string / foo /} "]; затем эхо "Это там!" fi` полезен при использовании золы оболочки BusyBox. Принятое решение не работает с BusyBox, потому что некоторые регулярные выражения bash не реализованы.
  • 1
    неравенство различий. Довольно странная мысль! я люблю это
39

Итак, есть много полезных решений для вопроса - но что быстрее/использует наименьший ресурс?

Повторные тесты с использованием этого кадра:

/usr/bin/time bash -c 'a=two;b=onetwothree; x=100000; while [ $x -gt 0 ]; do TEST ; x=$(($x-1)); done'

Замена TEST каждый раз:

[[ $b =~ $a ]]           2.92user 0.06system 0:02.99elapsed 99%CPU

[ "${b/$a//}" = "$b" ]   3.16user 0.07system 0:03.25elapsed 99%CPU

[[ $b == *$a* ]]         1.85user 0.04system 0:01.90elapsed 99%CPU

case $b in *$a):;;esac   1.80user 0.02system 0:01.83elapsed 99%CPU

doContain $a $b          4.27user 0.11system 0:04.41elapsed 99%CPU

(doContain был в ответе Ф. Хури)

И для хихиканья:

echo $b|grep -q $a       12.68user 30.86system 3:42.40elapsed 19%CPU !ouch!

Таким образом, простая опция заместителя предикативно выигрывает ли в расширенном тесте или случае. Случай переносимый.

Трубопроводы до 100000 град предсказуемо болезненны! Старое правило об использовании внешних утилит не требуется.

  • 2
    Аккуратный ориентир. Убедил меня использовать [[ $b == *$a* ]] .
  • 0
    Если я правильно читаю, case выигрывает с наименьшим общим временем. Вы пропускаете звездочку после $b in *$a . Я получаю немного более быстрые результаты для [[ $b == *$a* ]] чем для case с исправленной ошибкой, но, конечно, это может зависеть и от других факторов.
Показать ещё 1 комментарий
25

Это также работает:

if printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  printf "Found needle in haystack"
fi

И отрицательный тест:

if ! printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  echo "Did not find needle in haystack"
fi

Я предполагаю, что этот стиль немного более классический, менее зависимый от особенностей оболочки Bash.

Аргумент -- - это чистая POSIX-паранойя, используемая для защиты от входных строк, подобных параметрам, например --abc или -a.

Примечание. В замкнутом цикле этот код будет намного медленнее, чем использование внутренних функций оболочки Bash, поскольку один (или два) отдельных процесса будут созданы и подключены через каналы.

  • 3
    ОП четко помечен с помощью bash.
  • 5
    ... но ОП не говорит, какая версия bash; Например, старые версии Bash (такие, как часто у Solaris) могут не включать эти новые функции Bash. (Я столкнулся с этой точной проблемой (сопоставление шаблонов bash не реализовано) в Solaris w / bash 2.0)
Показать ещё 8 комментариев
13

Как насчет этого:

text="   <tag>bmnmn</tag>  "
if [[ "$text" =~ "<tag>" ]]; then
   echo "matched"
else
   echo "not matched"
fi
  • 1
    = ~ для соответствия регулярному выражению, следовательно, слишком мощный для цели OP.
10

Как сказал Павел в своем сравнении производительности:

if echo "abcdefg" | grep -q "bcdef"; then
    echo "String contains is true."
else
    echo "String contains is not true."
fi

Это POSIX-совместимый, как "case" $string "in", предоставленный Marcus, но его немного легче читать, чем ответ на запрос в случае. Также обратите внимание, что это будет намного медленнее, чем использование аргумента case, как указал Павел, не используйте его в цикле.

9

Этот ответ в стеке переполнение был единственным, кто мог уловить пробелы и тире:

# For null cmd arguments checking   
to_check=' -t'
space_n_dash_chars=' -'
[[ $to_check == *"$space_n_dash_chars"* ]] && echo found
8

Один из них:

[ $(expr $mystring : ".*${search}.*") -ne 0 ] && echo 'yes' ||  echo 'no'
  • 2
    expr - это одна из тех утилит для швейцарских армейских ножей, которые обычно могут делать все, что вам нужно, как только вы поймете, как это сделать, но после внедрения вы никогда не сможете вспомнить, почему или как он делает то, что делает, поэтому вы никогда не трогаете его снова и надеетесь, что он никогда не перестанет делать то, что делает.
  • 0
    @michael_n и что не так с этим ответом? Я не понимаю ...
Показать ещё 2 комментария
7

Bash4+. Примечание: не использовать кавычки вызовет проблемы, когда слова содержат пробелы и т.д. Всегда указывайте в bash IMO.

Вот несколько примеров Bash4+:

Пример 1, проверьте "да" в строке (без учета регистра):

    if [[ "${str,,}" == *"yes"* ]] ;then

Пример 2, проверьте "да" в строке (без учета регистра):

    if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then

Пример 3, проверьте "да" в строке (с учетом регистра):

     if [[ "${str}" == *"yes"* ]] ;then

Пример 4, проверьте "да" в строке (с учетом регистра):

     if [[ "${str}" =~ "yes" ]] ;then

Пример 5, точное совпадение (с учетом регистра):

     if [[ "${str}" == "yes" ]] ;then

Пример 6, точное совпадение (без учета регистра):

     if [[ "${str,,}" == "yes" ]] ;then

Пример 7: точное совпадение:

     if [ "$a" = "$b" ] ;then

Пример 8, подстановочный знак соответствует.ext (без учета регистра):

     if echo "$a" | egrep -iq "\.(mp[3-4]|txt|css|jpg|png)" ; then

наслаждаться.

7
[[ $string == *foo* ]] && echo "It there" || echo "Couldn't find"
  • 0
    Я добавлю, что в конце echo "Couldn't find оператор echo "Couldn't find " - это хороший прием для возврата 0 состояний выхода для этих соответствующих команд.
  • 0
    @nicodjimenez вы не можете больше ориентироваться на статус выхода с этим решением. Статус выхода поглощается сообщениями о статусе ...
Показать ещё 1 комментарий
4

Мой .bash_profile и как я использовал grep если PATH включил мои 2 bin dirs, не добавляйте их

# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

U=~/.local.bin:~/bin

if ! echo "$PATH" | grep -q "home"; then
    export PATH=$PATH:${U}   
fi
  • 0
    Это ответ?
  • 0
    upvote. зачем пытаться узнать, как это делает Bash, когда grep, гораздо более мощный, более чем вероятно будет доступен. также немного расширим его, сопоставив со списком шаблонов: grep -q -E 'pattern1|...|patternN' .
4

Мне нравится sed.

substr="foo"
nonsub="$(echo "$string" | sed "s/$substr//")"
hassub=0 ; [ "$string" != "$nonsub" ] && hassub=1

Изменить, логика:

  • Использовать sed для удаления экземпляра подстроки из строки

  • Если новая строка отличается от старой строки, существует подстрока

  • 1
    Пожалуйста, добавьте некоторые объяснения. Передача базовой логики важнее, чем просто предоставление кода, потому что это помогает ОП и другим читателям самим решить эту и подобные проблемы.
4

grep -q полезен для этой цели.

То же самое с помощью awk:

string="unix-bash 2389"
character="@"
printf '%s' "$string" | awk -vc="$character" '{ if (gsub(c, "")) { print "Found" } else { print "Not Found" } }'

Вывод:

Не найдено

string="unix-bash 2389"
character="-"
printf '%s' "$string" | awk -vc="$character" '{ if (gsub(c, "")) { print "Found" } else { print "Not Found" } }'

Вывод:

Найдено

Исходный источник: http://unstableme.blogspot.com/2008/06/bash-search-letter-in-string-awk.html

  • 1
    echo не переносим, вы должны использовать printf '%s' "$string" . Я редактирую ответ, потому что пользователь, кажется, больше не существует.
3

Мне часто приходилось выполнять эту функцию, поэтому я использую домашнюю функцию оболочки в моем .bashrc, как это, что позволяет мне использовать ее так часто, как мне нужно, с легко запоминающимся имя:

function stringinstring()
{
    case "$2" in 
       *"$1"*)
          return 0
       ;;
    esac   
    return 1
}

Чтобы проверить, содержится ли $string1 (скажем, abc) в $string2 (скажем, 123abcABC), мне просто нужно запустить stringinstring "$string1" "$string2" и проверить возвращаемое значение, например

stringinstring "$str1" "$str2"  &&  echo YES  ||  echo NO
  • 0
    [["$ str" == $ substr ]] && echo YES || эхо НЕТ
  • 0
    Я почти уверен, что x хак требуется только для очень старых оболочек.
2

Точное совпадение слов:

string='My long string'
exactSearch='long'

if grep -E -q "\b${exactSearch}\b" <<<${string} >/dev/null 2>&1
  then
    echo "It there"
  fi
2

Попробуйте oobash - это строковая библиотека стиля OO для bash 4. Она поддерживает немецкие умлауты. Он написан в bash. Доступны многие функции: -base64Decode, -base64Encode, -capitalize, -center, -charAt, -concat, -contains, -count, -endsWith, -equals, -equalsIgnoreCase, -reverse, -hashCode, -indexOf, -isAlnum, -isAlpha, -isAscii, -isDigit, -isEmpty, -isHexDigit, -isLowerCase, -isSpace, -isPrintable, -isUpperCase, -isVisible, -lastIndexOf, -length, -matches, -replaceAll, -replaceFirst, -startsWith, -substring, -swapCase, -toLowerCase, -toString, -toUpperCase, -trim и -zfill.

Посмотрите пример сложения:

[Desktop]$ String a testXccc                                                  
[Desktop]$ a.contains tX                   
true                                                           
[Desktop]$ a.contains XtX      
false      

oobash доступен на Sourceforge.net.

1

Я использую эту функцию (одна зависимость не включена, но очевидна). Он проходит тесты, показанные ниже. Если функция возвращает значение > 0, то строка была найдена. Вы также можете легко вернуть 1 или 0.

function str_instr {
   # Return position of ```str``` within ```string```.
   # >>> str_instr "str" "string"
   # str: String to search for.
   # string: String to search.
   typeset str string x
   # Behavior here is not the same in bash vs ksh unless we escape special characters.
   str="$(str_escape_special_characters "${1}")"
   string="${2}"
   x="${string%%$str*}"
   if [[ "${x}" != "${string}" ]]; then
      echo "${#x} + 1" | bc -l
   else
      echo 0
   fi
}

function test_str_instr {
   str_instr "(" "'foo@host (dev,web)'" | assert_eq 11
   str_instr ")" "'foo@host (dev,web)'" | assert_eq 19
   str_instr "[" "'foo@host [dev,web]'" | assert_eq 11
   str_instr "]" "'foo@host [dev,web]'" | assert_eq 19
   str_instr "a" "abc" | assert_eq 1
   str_instr "z" "abc" | assert_eq 0
   str_instr "Eggs" "Green Eggs And Ham" | assert_eq 7
   str_instr "a" "" | assert_eq 0
   str_instr "" "" | assert_eq 0
   str_instr " " "Green Eggs" | assert_eq 6
   str_instr " " " Green "  | assert_eq 1
}
0

На расширение вопроса ответили здесь https://stackoverflow.com/questions/2829613/how-do-you-tell-if-a-string-contains-another-string-in-unix-shell-scripting

Это решение работает со специальными символами:

# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"

    if echo "$string" | $(type -p ggrep grep | head -1) -F -- "$substring" >/dev/null; then
        return 0    # $substring is in $string
    else
        return 1    # $substring is not in $string
    fi
}

contains "abcd" "e" || echo "abcd does not contain e"
contains "abcd" "ab" && echo "abcd contains ab"
contains "abcd" "bc" && echo "abcd contains bc"
contains "abcd" "cd" && echo "abcd contains cd"
contains "abcd" "abcd" && echo "abcd contains abcd"
contains "" "" && echo "empty string contains empty string"
contains "a" "" && echo "a contains empty string"
contains "" "a" || echo "empty string does not contain a"
contains "abcd efgh" "cd ef" && echo "abcd efgh contains cd ef"
contains "abcd efgh" " " && echo "abcd efgh contains a space"

contains "abcd [efg] hij" "[efg]" && echo "abcd [efg] hij contains [efg]"
contains "abcd [efg] hij" "[effg]" || echo "abcd [efg] hij does not contain [effg]"

contains "abcd *efg* hij" "*efg*" && echo "abcd *efg* hij contains *efg*"
contains "abcd *efg* hij" "d *efg* h" && echo "abcd *efg* hij contains d *efg* h"
contains "abcd *efg* hij" "*effg*" || echo "abcd *efg* hij does not contain *effg*"

Ещё вопросы

Сообщество Overcoder
Наверх
Меню