如何检查字符串在bash中是否包含子字符串

 2019-04-19 

How to check if a string contains a substring in Bash

我在bash中有一个字符串:

1
string="My string"

如何测试它是否包含另一个字符串?

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

其中??是我不认识的接线员。我用echo和grep吗?

1
2
3
if echo"$string" | grep 'foo'; then
  echo"It's there!"
fi

看起来有点笨拙。


如果使用双括号,也可以在case语句之外使用marcus的答案(*widcards):

1
2
3
4
string='My long string'
if [[ $string == *"My long"* ]]; then
  echo"It's there!"
fi

注意,针串中的空格需要放在双引号之间,并且*通配符应该在外部。


如果您喜欢regex方法:

1
2
3
4
5
6
string='My string';

if [[ $string =~ .*My.* ]]
then
   echo"It's there!"
fi


我不确定是否使用if语句,但您可以使用case语句获得类似的效果:

1
2
3
4
5
case"$string" in
  *foo*)
    # Do stuff
    ;;
esac


兼容答案

由于已经有很多使用bash特定特性的答案,所以有一种方法可以在功能较差的shell下工作,比如busybox:

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

实际上,这可能会给:

1
2
3
4
5
6
7
8
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

这是在bash、dash、ksh和ash(busybox)下测试的,结果始终是:

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

变成一个函数

正如@eeroaltonen所要求的,这里有一个相同演示的版本,在相同的shell下测试:

1
2
3
4
5
6
7
8
9
10
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
}

然后:

1
2
3
4
5
$ 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'.

注意:您必须转义或双圈引号和/或双引号:

1
2
3
4
5
$ 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'.

简单函数

这是在busybox,dash,当然还有bash下测试的:

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

全是伙计们!

那么现在:

1
2
3
4
$ 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所指出的那样,函数将变为:

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

或者按照Adrian G护nter的建议,避免-o开关:

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

使用空字符串:

1
2
3
4
$ if stringContain '' ''; then echo yes; else echo no; fi
yes
$ if stringContain 'o"M' ''; then echo yes; else echo no; fi
no


您应该记住,shell脚本不是一种语言,而是一组命令。你本能地认为这种"语言"要求你跟随一个if和一个[或一个[[。这两个命令都只是返回指示成功或失败的退出状态的命令(就像其他所有命令一样)。因此,我会使用grep,而不是[命令。

只做:

1
2
3
if grep -q foo <<<"$string"; then
    echo"It's there"
fi

现在,您考虑将if作为测试它后面的命令(用分号完成)的退出状态。为什么不重新考虑正在测试的字符串的来源?

1
2
3
4
5
6
7
8
## 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不输出任何内容,因为我们只需要返回代码。<<<使shell扩展下一个单词,并将其用作命令的输入,这是<<的单行版本,这里是文档(我不确定这是标准的还是bashim的)。


公认的答案是最好的,但由于有多种方法可以做到这一点,下面是另一种解决方案:

1
2
3
if ["$string" !="${string/foo/}" ]; then
    echo"It's there!"
fi

${var/search/replace}$var,如果发现(不改变$var的话,用replace代替search。如果你试图用任何东西替换foo,而字符串已经改变,那么很明显,foo被找到了。


所以有很多有用的解决方案来解决这个问题——但是哪一个最快/使用最少的资源?

使用此框架的重复测试:

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

每次更换测试:

1
2
3
4
5
6
7
8
9
[[ $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

(多康坦在F·胡里的回答中)

对于咯咯笑:

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

因此,无论是在扩展测试中还是在案例中,简单的替换选项都是可以预见的。这个箱子是便携式的。

达到100000个greps是可以预见的痛苦!关于无需使用外部实用程序的旧规则是正确的。


这也适用于:

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

阴性试验为:

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

我认为这种风格更经典一点——不太依赖于bash shell的特性。

--参数是纯posix偏执狂,用于保护输入字符串不受与选项类似的影响,如--abc-a

注意:在一个紧凑的循环中,此代码将比使用内部bash shell特性慢得多,因为将创建一个(或两个)单独的进程并通过管道进行连接。


这个怎么样?

1
2
3
4
5
6
text="   <tag>bmnmn</tag> "
if [["$text" =~"<tag>" ]]; then
   echo"matched"
else
   echo"not matched"
fi


bash4+示例。注意:不使用引号会导致单词包含空格等问题。总是引用bash imo的话。

下面是一些示例bash4+:

示例1,检查字符串中的"是"(不区分大小写):

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

示例2,检查字符串中的"是"(不区分大小写):

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

示例3,检查字符串中的"是"(区分大小写):

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

示例4,检查字符串中的"是"(区分大小写):

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

示例5,完全匹配(区分大小写):

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

示例6,完全匹配(不区分大小写):

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

例7,完全匹配:

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

示例8,通配符match.ext(不区分大小写):

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

享受。


正如保罗在他的业绩比较中提到的:

1
2
3
4
5
if echo"abcdefg" | grep -q"bcdef"; then
    echo"String contains is true."
else
    echo"String contains is not true."
fi

这与Marcus提供的'case"$string"答案类似,是POSIX兼容的,但比case-statement答案更容易阅读。还要注意,这将比使用case语句慢得多,正如Paul指出的,不要在循环中使用它。


此堆栈溢出答案是唯一一个捕获空格和短划线字符的答案:

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

一个是:

1
[ $(expr $mystring :".*${search}.*") -ne 0 ] && echo 'yes' ||  echo 'no'


1
[[ $string == *foo* ]] && echo"It's there" || echo"Couldn't find"


grep -q可用于此目的。

使用awk的方法相同:

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

输出:

Not Found

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

输出:

Found

原始来源:http://unstableme.blogspot.com/2008/06/bash-search-letter-in-string-awk.html


我喜欢SeD。

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

编辑,逻辑:

  • 使用sed从字符串中移除子字符串的实例

  • 如果新字符串与旧字符串不同,则存在子字符串


我的.bash_简介以及我如何使用grep如果路径中包含我的2个bin dirs,请不要附加它们

1
2
3
4
5
6
7
8
9
10
11
# .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


我发现经常需要这个功能,所以我在我的.bashrc中使用了一个自制的shell函数,它允许我尽可能频繁地重复使用它,并有一个易于记忆的名称:

1
2
3
4
5
6
7
8
9
function stringinstring()
{
    case"$2" in
       *"$1"*)
          return 0
       ;;
    esac  
    return 1
}

为了测试$string1是否包含在$string2中(比如123abc abc),我只需要运行stringinstring"$string1""$string2"并检查返回值,例如

1
stringinstring"$str1""$str2"  &&  echo YES  ||  echo NO


此处回答问题的扩展:https://stackoverflow.com/a/8811800/712666

此解决方案使用特殊字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 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*"


尝试oo bash它是一个用于bash 4的oo样式字符串库。它支持德国Umlauts。它是用bash写的。Many functions are available: -base64Decode, -base64Encode, -capitalize, -center, -charAt, -concat, -contains, -count, -endsWith, -equals, -equalsIgnoreCase, -reverse, -hashCode, -indexOf, -isAlnum, -isAlpha, -isAscii, -isDigit, -isEmpty, -isHexDigit,edoccx1〔39〕,-isSpace-isPrintable-isUpperCase-isVisible-lastIndexOf-length-matches-replaceAll-replaceFirst-startsWith-substring-swapCase,EDOCX11〔52〕,edcx1〔53〕,-toString-toString-lastIndexOf-length-matches-replaceAll-replaceFirst-replaceFirst-replaceFirst-startsWith-toUpperCase-trim-zfill

查看包含示例:

1
2
3
4
5
[Desktop]$ String a testXccc                                                  
[Desktop]$ a.contains tX                  
true                                                          
[Desktop]$ a.contains XtX      
false

oobash在sourceforge.net上提供。


完全匹配的单词:

1
2
3
4
5
6
7
string='My long string'
exactSearch='long'

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

我使用这个函数(不包括一个依赖项,但很明显)。它通过了下面所示的测试。如果函数返回的值大于0,则会找到字符串。您可以很容易地返回1或0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
}