题 如何检查字符串是否包含Bash中的子字符串


我在Bash中有一个字符串:

string="My string"

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

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

哪里 ?? 是我未知的运营商。我是否使用echo和 grep

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

这看起来有点笨拙。


1749
2017-10-23 12:37


起源


嗨,如果空字符串是假的,为什么你认为它很笨拙?尽管提出了解决方案,但这是唯一对我有用的方法。 - ericson.cepeda
你可以使用 expr 命令在这里 - cli__
这是posix shell的一个: stackoverflow.com/questions/2829613/... - sehe


答案:


您可以使用 马库斯的回答(*通配符) 在case语句之外,如果你使用双括号:

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

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


2504
2017-10-23 12:55



另请注意,您可以通过在测试中切换到!=来反转比较。感谢你的回答! - Quinn Taylor
@Jonik:你可能会错过shebang或将其视为 #!/bin/sh。尝试 #!/bin/bash 代替。 - Dennis Williamson
在括号和内容之间留一个空格。 - Paul Price
您不需要在[[]]中引用变量。这也可以:[[$ string == $针 ]] && echo发现 - Orwellophile
@Orwellophile 小心! 你只能在第一个参数中省略双引号 [[。看到 ideone.com/ZSy1gZ - nyuszika7h


如果您更喜欢正则表达式方法:

string='My string';

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

388
2017-10-23 20:10



不得不在bash脚本中替换egrep正则表达式,这非常有效! - blast_hardcheese
如果你需要一个空格,用反斜杠来逃避它: [[ $string =~ My\ s ]] - Matt Tardiff
该 =~ 运算符已经在整个字符串中搜索匹配项;该 .*这里是无关紧要的。此外,引号通常优于反斜杠: [[ $string =~ "My s" ]] - bukzor
@bukzor报价从Bash 3.2+开始停止工作: tiswww.case.edu/php/chet/bash/FAQ  E14)。最好分配给变量(使用引号),然后进行比较。喜欢这个: re="My s"; if [[ $string =~ $re ]] - seanf
测试一下 不 包含一个字符串: if [[ ! "abc" =~ "d" ]] 是真的。 - KrisWebDev


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

case "$string" in 
  *foo*)
    # Do stuff
    ;;
esac

240
2017-10-23 12:47



这可能是最好的解决方案,因为它可以移植到posix shell。 (a.k.a. no bashisms) - technosaurus
@technosaurus我觉得在一个只有bash标签的问题上批评“bashism”是相当奇怪的:) - P.P.
@ P.P。与其说是更普遍的解决方案偏好于更有限的解决方案并不是一种批评。请注意,多年以后,人们(像我一样)会停下来寻找这个答案,并且可能很高兴找到一个比原始问题范围更广泛的答案。正如他们在开源世界中所说:“选择很好!” - Carl Smotricz
@technosaurus,FWIW [[ $string == *foo* ]] 也适用于某些符合POSIX标准的sh版本(例如 /usr/xpg4/bin/sh 在Solaris 10上)和ksh(> = 88) - maxschlepzig
@maxschlepzig ...如果$ IFS中有字符,例如空格,换行符,制表符或用户定义的分隔符,则不会...虽然没有奇怪的混合引用或者不得不推送和弹出IFS - technosaurus


兼容的答案

由于已经有很多使用Bash特定功能的答案,因此有一种方法可以在功能较差的shell下工作

[ -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

这是在测试下  和  (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所说,这是一个相同的演示版本,在相同的shell下测试:

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" ]; }

或者按照建议 AdrianGünter的评论,避免 -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

137
2017-12-08 23:06



如果你能想出一些方法将它放到一个函数中,这将会更好。 - Eero Aaltonen
@EeroAaltonen你如何找到我的(新添加的)功能? - F. Hauri
我知道!找 。 -name“*”| xargs grep“myfunc”2> / dev / null - eggmatters
这很棒,因为它非常兼容。但是有一个错误:如果haystack字符串为空,它就不起作用。正确的版本将是 string_contains() { [ -z "${2##*$1*}" ] && [ -n "$2" -o -z "$1" ]; }最后的想法:空字符串是否包含空字符串?上面的版本是肯定的(因为 -o -z "$1" 部分)。 - Sjlver
+1。很好!对我来说,我改变了命令stringContain(){[ - z“$ {1 ## * $ 2 *}”] && [-z“$ 2”-o -n“$ 1”]; }; “搜索在哪里”“找到什么”。在busybox中工作。上面接受的答案在busybox中不起作用。 - user3439968


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

做就是了:

if grep -q foo <<<"$string"; then
    echo "It's 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不输出任何内容,因为我们只需要返回代码。 <<< 使shell扩展下一个单词并将其用作命令的输入,该单行版本 << 这里的文件(我不确定这是标准还是基础)。


110
2017-10-27 14:57



他们叫 这里的字符串(3.6.7) 我相信这是基础 - alex.pilon
一个也可以使用 流程替代  if grep -q foo <(echo somefoothing); then - larsr
这样做的代价是非常昂贵的:做 grep -q foo <<<"$mystring" implie 1 fork and is bashism 和 echo $mystring | grep -q foo 2个叉子(一个用于管道,第二个用于运行) /path/to/grep) - F. Hauri
我们对“成本”和“开销”的看法在我们出生的十年中受到了扭曲。我很久以前参加过'内置测试'诉'exec grep'战争,但今天这样的战争仅仅是迂腐的棋盘游戏。你的40亿硅晶体管只是坐在那里乞讨 某物 要做(最有可能泄漏电池电流)。帐篷中的长杆是远射的正确性。附:我同意你的评论:) - qneill
@qneill,我们关于效率无关紧要的观点被表演辩护者后现代主义叙事所扭曲。只有浪费CPU的功率,不需要任何类似并发的东西,忽略真相只是一种不便。今天的现代办公需要并发,大约99年将被称为互联网规模。电话大小的通用计算是一种常态,也不例外。因此,注意实现效率不应该要求神圣的Ruby爱好者熨平板。 - TechZilla


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

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

${var/search/replace} 是 $var 与第一个实例 search 取而代之 replace,如果找到(它没有改变 $var)。如果你试图更换 foo 什么都没有,而且字符串已经改变了,显然 foo 被找到。


73
2017-10-23 14:35



以上ephemient的解决方案:>`if [“$ string”!=“$ {string / foo /}”];然后回应“它在那里!”当使用BusyBox的shell时,fi`非常有用 灰。已接受的解决方案不适用于BusyBox,因为某些bash的正则表达式未实现。 - TPoschel
这是一种解决问题的扭曲方式,但这是一个很好的技术。 - Sébastien
差异的不平等。很奇怪的想法!我喜欢它 - nitinr708


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

使用此框架重复测试:

/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在F. Houri的回答中)

而对于咯咯地笑:

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

因此,无论是在扩展测试还是案例中,简单替代选项都可以获得胜利。外壳是便携式的。

管道输出到100000 greps是可以预料的痛苦!关于无需使用外部实用程序的旧规则是正确的。


31
2017-08-27 19:42



整洁的基准。说服我使用 [[ $b == *$a* ]]。 - Mad Physicist
如果我正确读到这个, case 以最小的总时间消耗获胜。你错过了一个星号 $b in *$a 虽然。我的结果略快一点 [[ $b == *$a* ]] 而不是 case 纠正错误,但当然也可能取决于其他因素。 - tripleee
ideone.com/5roEVt 我的实验修复了一些额外的错误并测试了不同的场景(其中字符串实际上不存在于较长的字符串中)。结果大致相似; [[ $b == *$a* ]] 很快 case 几乎一样快(兼容POSIX兼容)。 - tripleee