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

3490

我在Bash中有一个字符串:

string="My string"

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

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

其中??是我未知的运算符,我该使用echogrep吗?

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

看起来有点笨拙。


4
嗨,如果空字符串是假的,为什么你认为它笨拙?尽管有提出的解决方案,但这是我唯一有效的方法。 - ericson.cepeda
1
你可以在这里使用 expr 命令。 - cifer
9
这是一个适用于posix shell的例子:https://dev59.com/yXE85IYBdhLWcg3wVR6g - sehe
2
请在您的示例中使用“$haystack中的*$needle*”习语。这样更容易阅读和理解。 - Piotr Henryk Dabrowski
30个回答

20
这样怎么样:
text="   <tag>bmnmn</tag>  "
if [[ "$text" =~ "<tag>" ]]; then
   echo "matched"
else
   echo "not matched"
fi

2
=~ 用于正则表达式匹配,因此对于 OP 的目的来说过于强大。 - user49586

19

被采纳的答案是正确的,但很难阅读和理解。
对于与搜索相关的问题,您应始终使用$needle in a $haystack习语。
由于其建议的编辑队列已满,我发布了这个内容:

haystack='There are needles here.'
if [[ "$haystack" == *"needle"* ]]; then
    echo "It's there!"
fi

必须说明的是,这仅在 bash 下工作,当用于脚本文件时,必须以 #!/bin/bash 开头。 - Sam Sirry

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

我想补充一下,结尾处的 echo "Couldn't find 语句是一个很好的技巧,可以为这些匹配命令返回0的退出状态。 - nicodjimenez
@nicodjimenez,使用此解决方案无法再针对退出状态进行目标定位。退出状态被状态消息所吞噬... - Jahid
1
这正是我所说的... 如果你没有 || echo "Couldn't find",那么如果没有匹配,你将返回一个错误退出状态,如果你正在运行持续集成管道等情况,你可能不想要这个错误退出状态。 - nicodjimenez

11

第一个问题是:

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

3
“expr”是那些瑞士军刀般的实用工具之一,通常可以做任何你需要做的事情,一旦你弄清楚了如何使用它,但一旦实施,你就永远不会记得它为什么或者它是如何做到它正在做的事情的,所以你再也不会碰它了,并希望它永远不会停止它正在做的事情。 - michael
@AloisMahdal 我从未点过踩,我只是在推测为什么会有人点踩。这是一个警示性评论。我确实偶尔使用expr,当可移植性阻止使用bash(例如,在旧版本中行为不一致),tr(无处不在的不一致)或sed(有时太慢)时。但从个人经验来看,每当重新阅读这些expr-isms时,我都必须回到man页面。因此,我建议对expr的每个用法进行注释... - michael
2
曾经有一个时代,你只能使用原始的Bourne shell。它缺少一些常用的功能,因此实现了像exprtest这样的工具来执行它们。在今天,通常有更好的工具,其中许多内置于任何现代shell中。我猜test仍然存在,但似乎没有人想念expr - tripleee
1
点赞,因为我需要在Bourne shell中运行的东西,而其他所有东西似乎都是特定于bash的。 - Svet
expr: syntax error: unexpected argument ‘.*.*’ bash: [: -ne: unary operator expected - Nairum
这是一种非常复杂的方式去实现一些简单的事情。 - Mike Q

11

这篇 Stack Overflow 的答案 是唯一一个捕获空格和破折号的答案:

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

3
这是对同一个问题的答案。 - Peter Mortensen

7

由于POSIX/BusyBox问题没有提供正确答案(在我看来),我将在此发布一个答案。

最简短的答案是:

[ ${_string_##*$_substring_*} ] || echo Substring found!

或者

[ "${_string_##*$_substring_*}" ] || echo 'Substring found!'

请注意,在某些shell(ash)中,双井号强制性的。上面的代码将在未找到子字符串时评估[ stringvalue ]。它不会返回错误。当找到子字符串时,结果为空,并且它会评估[ ]。由于*,这将抛出错误代码1,因为字符串被完全替换。 最短常见语法:
[ -z "${_string_##*$_substring_*}" ] && echo 'Substring found!'

或者

[ -n "${_string_##*$_substring_*}" ] || echo 'Substring found!'

另一个:

[ "${_string_##$_substring_}" != "$_string_" ] && echo 'Substring found!'

或者

[ "${_string_##$_substring_}" = "$_string_" ] || echo 'Substring found!'

注意单个等号!

6
通用的“针在沙堆中寻找”示例随后跟随有变量。
#!/bin/bash

needle="a_needle"
haystack="a_needle another_needle a_third_needle"
if [[ $haystack == *"$needle"* ]]; then
    echo "needle found"
else
    echo "needle NOT found"
fi

6

我的.bash_profile文件以及我如何使用grep:

如果PATH环境变量包括我的两个bin目录,则不要追加它们。

# .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

1
这是一个答案吗? - codeforester
点赞。为什么要学习Bash如何做,当更强大的grep很可能会提供。还可以通过匹配模式列表来进一步扩展它:grep -q -E 'pattern1|...|patternN' - Mohamed Bana

6

这是对此问题的扩展回答:如何在POSIX sh中判断一个字符串是否包含另一个字符串?:

这个解决方案适用于特殊字符:

# 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*"

2
这里不能使用测试 contains "-n" "n",因为 echo -n 会将 -n 当作选项而忽略它!一个常见的解决方法是使用 printf "%s\n" "$string" - joeytwiddle
1
我已经修复了你链接的答案,现在它可以处理特殊字符了。正如@midnite指出的那样,在使用POSIX子字符串参数扩展时,它所需要的只是在子字符串周围加上双引号。我还借鉴了你的一些测试用例。谢谢,Alex。 - fjarlq

5

我喜欢使用sed。

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

编辑,逻辑:

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

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


4
请添加一些解释。传授潜在的逻辑比仅仅给出代码更重要,因为它有助于问题提出者和其他读者自行解决这些及类似问题。 - Zulan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接