如何在 POSIX sh 中判断一个字符串是否包含另一个字符串?

178

我想编写一个Unix shell脚本,如果一个字符串包含在另一个字符串中,就执行各种逻辑。例如,如果我在某个特定的文件夹中,就要进行分支处理。请问有人能告诉我如何实现这个功能吗?如果可能的话,我希望这个脚本不仅限于特定的shell(即不仅仅是bash),但如果没有其他方法,那我也可以接受。

#!/usr/bin/env sh

if [ "$PWD" contains "String1" ]
then
    echo "String1 present"
elif [ "$PWD" contains "String2" ]
then
    echo "String2 present"
else
    echo "Else"
fi

3
我知道这篇文章有些过时了,但是对于后来的访问者而言,以下几点需要注意:(1) 通常最好将 SNAKE_CASE 变量名称保留给环境和 shell 内部变量。 (2) 设置CURRENT_DIR是多余的,您可以直接使用 $PWD - alexia
12个回答

218

这里有另一种解决方案。它使用POSIX子字符串参数扩展,因此可以在Bash、Dash、KornShell(ksh)、Z shell(zsh)等中运行。它还支持字符串中的特殊字符。

test "${string#*"$word"}" != "$string" && echo "$word found in $string"

带有一些测试的功能化版本:

# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"
    if [ "${string#*"$substring"}" != "$string" ]; then
        return 0    # $substring is in $string
    else
        return 1    # $substring is not in $string
    fi
}

testcontains() {
    testnum="$1"
    expected="$2"
    string="$3"
    substring="$4"
    contains "$string" "$substring"
    result=$?
    if [ $result -eq $expected ]; then
        echo "test $testnum passed"
    else
        echo "test $testnum FAILED: string=<$string> substring=<$substring> result=<$result> expected=<$expected>"
    fi
}

testcontains  1 1 'abcd' 'e'
testcontains  2 0 'abcd' 'ab'
testcontains  3 0 'abcd' 'bc'
testcontains  4 0 'abcd' 'cd'
testcontains  5 0 'abcd' 'abcd'
testcontains  6 1 '' 'a'
testcontains  7 0 'abcd efgh' 'cd ef'
testcontains  8 0 'abcd efgh' ' '
testcontains  9 1 'abcdefgh' ' '
testcontains 10 0 'abcd [efg] hij' '[efg]'
testcontains 11 1 'abcd [efg] hij' '[effg]'
testcontains 12 0 'abcd *efg* hij' '*efg*'
testcontains 13 0 'abcd *efg* hij' 'd *efg* h'
testcontains 14 1 'abcd *efg* hij' '*effg*'
testcontains 15 1 'abcd *efg* hij' '\effg\'
testcontains 16 0 'a\b' '\'
testcontains 17 0 '\' '\'
testcontains 18 1 '[' '\'
testcontains 19 1 '\' '['
testcontains 20 0 '-n' 'n'
testcontains 21 1 'n' '-n'
testcontains 22 0 '*\`[]' '\`'

3
如果子字符串中包含反斜杠,则此方法对我无效。像往常一样,“substring =“ $(printf'% q'“$ 2”)”能够解决问题。 - Egor Tensin
13
问题在于双括号不是 POSIX,这是问题提出的前提,@Pablo。 - Rob Kennedy
3
这不适用于像[]这样的特殊字符。请参见我的答案https://dev59.com/znVC5IYBdhLWcg3woStW#54490453。 - Alex Skrypnyk
2
我们必须双引号匹配的 "$word"。https://www.shellcheck.net/wiki/SC2295 - midnite
2
谢谢,@midnite。ShellCheck很酷。我已经编辑了这篇文章,现在它支持特殊字符了。同时感谢Alex Skrypnyk和Egor Tensin指出了同样的错误。 - fjarlq
显示剩余2条评论

117

纯粹的 POSIX shell:

#!/bin/sh
CURRENT_DIR=`pwd`

case "$CURRENT_DIR" in
  *String1*) echo "String1 present" ;;
  *String2*) echo "String2 present" ;;
  *)         echo "else" ;;
esac

像ksh或bash这样的扩展shell拥有花式匹配机制,但是老式的 case 语句也同样强大。


51
#!/usr/bin/env sh

# Searches a subset string in a string:
# 1st arg:reference string
# 2nd arg:subset string to be matched

if echo "$1" | grep -q "$2"
then
    echo "$2 is in $1"
else 
    echo "$2 is not in $1"
fi

3
grep -q "$2" 改为 grep -q "$2" > /dev/null 可以避免不必要的输出。 - Victor Sergienko
3
е∞Жgrep -q "$2"дњЃжФєдЄЇgrep -q "$2" 2>/dev/nullдї•йБњеЕНдЇІзФЯдЄНењЕи¶БзЪДиЊУеЗЇгАВ - ulidtko
grep 不是 POSIX。 - Adel M.
2
@AdelM。你确定吗?https://www.unix.com/man-page/posix/1p/grep/ - Tripp Kinetics
1
@TrippKinetics。我错了,你是对的。 - Adel M.
最终在Jenkins中运行成功了!! - Vineeth

38

遗憾的是,我不知道如何在sh中实现这一点。但是,使用bash(从版本3.0.0开始,这可能是您拥有的版本),您可以像这样使用=~运算符:

#!/bin/bash
CURRENT_DIR=`pwd`

if [[ "$CURRENT_DIR" =~ "String1" ]]
then
 echo "String1 present"
elif [[ "$CURRENT_DIR" =~ "String2" ]]
then
 echo "String2 present"
else
 echo "Else"
fi

作为额外的奖励(或警告,如果您的字符串中有任何有趣的字符),=~将接受正则表达式作为右操作数,如果您省略引号。


3
不要引用正则表达式,否则它通常会无法工作。例如,尝试[[ test =~ "test.*" ]][[ test =~ test.* ]]的区别。 - l0b0
1
好的,如果你正在测试一个子字符串,它将完美地工作,就像原来的问题一样,但它不会将右操作数视为正则表达式。我会更新我的答案,使其更加清晰。 - John Hyland
9
这是 Bash,而不是问题所要求的 POSIX sh。 - Reid

18
case $(pwd) in
  *path) echo "ends with path";;
  path*) echo "starts with path";;
  *path*) echo "contains path";;
  *) echo "this is the default";;
esac

11

这里是一个链接,包含了各种解决您问题的方案。

以下是我最喜欢的一种方法,因为它最容易被人读懂:

星号通配符法

if [[ "$string" == *"$substring"* ]]; then
    return 1
fi
return 0

sh 中,我收到了“未知操作数”的错误。但在 Bash 中可以正常工作。 - halfer
33
“[[”不符合POSIX标准。 - Ian
这在IBM z/OS USS POSIX shell中有效。 - zarchasmpgmr
虽然对我来说在Jenkins上不起作用 - undefined

6

这里有Bash正则表达式的内容。或者可以使用'expr'命令:

 if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi

3

如果您想使用仅限于ksh的方法,且速度与“test”一样快,您可以尝试以下方式:

contains() # haystack needle
{
    haystack=${1/$2/}
    if [ ${#haystack} -ne ${#1} ] ; then
        return 1
    fi
    return 0
}

它的工作原理是在草堆中删除针头,然后比较旧草堆和新草堆的字符串长度。


1
能不能返回test的结果呢?比如 return [ ${#haystack} -eq ${#1} ] - Alexis Wilke
是的,没错。我会保留它,因为对于外行人来说更容易理解。如果你在代码中使用,请使用@AlexisWilke的方法。 - JoeOfTex

2

请查看“test”程序的手册。如果您仅想测试目录是否存在,通常可以这样操作:

if test -d "String1"; then
  echo "String1 present"
end

如果您想匹配一个字符串,也可以使用bash扩展规则和通配符:

if test -d "String*"; then
  echo "A directory starting with 'String' is present"
end

如果你需要做更复杂的操作,你需要使用像expr这样的其他程序。


1
你的第二个例子中似乎有一个错误的双引号(")。 - Alexis Wilke

2
test $(echo "stringcontain" "ingcon" |awk '{ print index($1, $2) }') -gt 0 && echo "String 1 contain string 2"

--> 输出:字符串1包含字符串2


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