在Bash中,我如何检查一个字符串是否以某个值开头?

1133
我想检查一个字符串是否以"node"开头,例如"node001"。类似这样的操作。
if [ $HOST == node* ]
  then
  echo yes
fi

我该如何正确地做呢?


我还需要结合表达式来检查HOST是否为"user1"或以"node"开头。
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

怎样才能正确地做到呢?

7
不要过于诱惑将表达式结合在一起。虽然两个分离的条件语句可能看起来不那么美观,但这样可以提供更好的错误消息,并使脚本更易于调试。另外,我建议避免使用bash特性,而是使用switch语句。 - hendry
13个回答

1532

这段代码片段来自Advanced Bash Scripting Guide,它表示:

# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.

[[ $a == z* ]]   # True if $a starts with a "z" (wildcard matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

你几乎是正确的;你需要使用双括号而不是单括号。


关于你的第二个问题,你可以这样写:

HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes2
fi

哪一个将会回显

yes1
yes2

Bash的if语法很难适应(在我看来)。


23
你是说正则表达式的意思是[[ $a =~ ^z.* ]]吗? - JStrahl
4
[[ $a == z* ]][[ $a == "z*" ]] 之间是否有功能上的差异?换句话说,它们的工作方式是否不同?当你说 "$a 等于 z*" 时,具体是什么意思? - Niels Bom
9
如果你将“then”放在单独的一行,就不需要语句分隔符“;”。 - Yaza
15
完整版翻译如下:为了完整起见,检查一个字符串是否以...结尾的方法是:[[ $a == *com ]]。请注意,此处的 * 表示任意数量的字符。 - lepe
7
ABS 是一个不幸的参考选择 -- 它很像 bash 的 W3Schools,充斥着过时的内容和不良实践的例子;自 2008 年以来,freenode #bash 频道一直在试图阻止其使用(http://wooledge.org/~greybot/meta/abs)。能否考虑将其重新指向 BashFAQ #31?(我也建议使用 Bash-Hackers' wiki,但它已经停止运营了一段时间) - Charles Duffy
显示剩余15条评论

366

如果您正在使用较新版本的Bash(v3+),我建议使用Bash正则表达式比较运算符=~,例如:

if [[ "$HOST" =~ ^user.* ]]; then
    echo "yes"
fi

为了在正则表达式中匹配this or that,使用|,例如:

if [[ "$HOST" =~ ^user.*|^host1 ]]; then
    echo "yes"
fi

注意 - 这是“正确”的正则表达式语法。
- user* 表示 use 和零个或多个 r,因此将匹配 useuserrrr。 - user.* 表示 user 和零个或多个任意字符,因此将匹配 user1userX。 - ^user.* 表示在 $HOST 的开头匹配模式 user.*
如果您不熟悉正则表达式语法,请参考这个资源
请注意,Bash 的 =~ 操作符只有在右侧未加引号时才执行正则表达式匹配。如果您对右侧进行引号包含,则“模式的任何部分都可以引用以强制其作为字符串匹配”。即使进行参数扩展,也不应引用右侧。

谢谢,Brabster!我在原帖中添加了一个新问题,关于如何在if语句中组合表达式。 - Tim
3
可惜获得认可的答案没有涉及正则表达式的语法。 - CarlosRos
67
注意:Bash 中的 "=~" 运算符只有在右侧未加引号时才进行正则表达式匹配。如果对右侧进行了引号包裹,则可引用任何部分以强制将其作为字符串进行匹配。因此,(1) 请确保始终将正则表达式放在未被引用的右侧;(2) 如果您将正则表达式存储在变量中,请确保在进行参数扩展时不要引用右侧。 - Trevor Boyd Smith

262

我总是尽量使用POSIX sh 而不是使用Bash扩展功能,因为脚本编写的一个主要目标是可移植性(除了连接程序,而不是替换它们)。

sh中,有一种简单的方法可以检查“前缀匹配”条件。

case $HOST in node*)
    # Your code here
esac

考虑到sh是古老的、晦涩难懂的(而Bash并不是解决方案:它更加复杂、不够一致和不够可移植),我想指出一个非常好的函数方面:虽然像case这样的语法元素是内置的,但是生成的结构与任何其他作业都没有区别。它们可以以相同的方式组合:

if case $HOST in node*) true;; *) false;; esac; then
    # Your code here
fi

甚至更短

if case $HOST in node*) ;; *) false;; esac; then
    # Your code here
fi

甚至更短(只是为了将!呈现为语言元素--但这是不好的风格)

if ! case $HOST in node*) false;; esac; then
    # Your code here
fi
如果您喜欢清晰明确,可以构建自己的语言元素:
beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

这实际上不是很好吗?

if beginswith node "$HOST"; then
    # Your code here
fi

因为sh基本上只涉及作业和字符串列表(以及内部过程,其中作业由此组成),所以我们现在甚至可以进行一些轻量级的函数式编程:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }

all() {
    test=$1; shift
    for i in "$@"; do
        $test "$i" || return
    done
}

all "beginswith x" x xy xyz ; checkresult  # Prints TRUE
all "beginswith x" x xy abc ; checkresult  # Prints FALSE

这很优雅。不过我并不建议在任何严肃的场合使用sh - 它很容易在真实世界的需求中出现问题(没有lambda,所以我们必须使用字符串。但是嵌套函数调用和管道是不可能的等等)。


26
不仅这是可携带的,还易于阅读、符合惯用语法和优雅(适用于shell脚本)。它也很容易扩展到多个模式; case $HOST in user01 | node* ) ... - tripleee
这种代码格式有一个名称吗? if case $HOST in node*) true;; *) false;; esac; then我在这里和那里看到过它,但在我看来它看起来有点紧凑。 - Niels Bom
@NielsBom 我不知道您所说的格式化具体是什么意思,但我的观点是shell代码非常具有可组合性。因为case命令是命令,它们可以放在if...then语句中。 - Jo So
我甚至不明白为什么它是可组合的,我对shell脚本的理解还不够深入 :-)我的问题是关于这段代码如何使用非匹配括号和双分号。它看起来与我以前见过的shell脚本完全不同,但可能是因为我更习惯于看bash脚本而不是sh脚本。 - Niels Bom
虽然 shell 的 case 语法一开始可能看起来古怪和令人困惑,但你很快就会习惯它。为了能够阅读其他人编写的脚本,即使你不打算自己使用它,你也应该掌握这个结构的基本知识。 (就我个人而言,在所有稍有不同的变体中,我倾向于使用它而不是 if [[ $string something...。POSIX 兼容性是一个不错的奖励。) - tripleee
显示剩余6条评论

113

您可以仅选择要检查的字符串部分:

if [ "${HOST:0:4}" = user ]

对于您的后续问题,您可以使用一个OR

if [[ "$HOST" == user1 || "$HOST" == node* ]]

9
你应该将${HOST:0:4}用双引号括起来。 - Jo So
@Jo So:是什么原因? - Peter Mortensen
1
@PeterMortensen,尝试 HOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi - Jo So
另外,也可以使用双方括号:if [[ ${HOST:0:4} = user ]] - Zim
@martin clayton 请发布整个if表达式。 - KansaiRobot
如果你想要匹配结尾,那么可以与"${HOST:0:-4}"进行比较,这样它就能检测到类似于"dev-user"或"prod-user"的内容。 - voldomazta

65

我更喜欢已经发布的其他方法,但有些人喜欢使用:

case "$HOST" in 
    user1|node*) 
            echo "yes";;
        *)
            echo "no";;
esac

编辑:

我已将您提供的备选项添加到上面的case语句中。

在您编辑后的版本中,括号过多。应该是这样的:

if [[ $HOST == user1 || $HOST == node* ]];

谢谢,Dennis!我在原帖中新增了一个问题,关于如何在if语句中结合表达式。 - Tim
13
一些人喜欢这个更容易在不同版本和Shell之间移植。 - carlosayam
使用case语句时,您可以省略变量周围的引号,因为不会发生单词拆分。我知道这是毫无意义和不一致的,但我喜欢在那里省略引号,使其在本地更具视觉吸引力。 - Jo So
在我的情况下,我不得不在 ) 前省略引号: "/") 不起作用,/) 起作用。(我正在寻找以 / 开头的字符串,即绝对路径) - Josiah Yoder

46

虽然我发现这里大多数答案相当正确,但其中许多包含不必要的Bashisms。 POSIX参数扩展 提供了您所需的一切:

[ "${host#user}" != "${host}" ]

[ "${host#node}" != "${host}" ]

${var#expr} 将从 ${var} 中删除与 expr 最短匹配的前缀并返回结果。因此,如果 ${host} 不以 usernode)开头,则 ${host#user}${host#node})与 ${host} 相同。

expr 允许使用fnmatch()通配符,因此${host#node??}等都可以使用。


3
我认为 [[ $host == user* ]] 可能比 [ "${host#user}" != "${host}" ] 更必要,因为前者更容易阅读。如果您控制脚本执行的环境(针对最新版本的bash),则应优先选择前者。 - x-yuri
3
老实说,我会把这个代码块放进一个 has_prefix() 函数中,并且永远不再查看它。 - dhke

40

由于在 Bash 中 # 有特殊意义,我得到了以下解决方案。

另外,我更喜欢使用 "" 包装字符串来避免空格等问题。

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi

1
这正是我所需要的。谢谢! - Ionică Bizău
谢谢,我也在想如何匹配以“blah:”开头的字符串,看起来这就是答案! - Anentropic
1
case $A in "#"*) echo "Skip comment line";; esac 既短又便携。 - tripleee
这是对问题的回复吗?我没有看到任何 sdfs... - KansaiRobot
KansaiRobot,它演示了如何在现金字符串比较中使用*。每个人都应该用他想要比较的字符串替换“sdfs”。 - ozma

16

将Mark Rushakoff的最高排名答案的语法细节添加一点更多的细节。

该表达式

$HOST == node*

也可以写作

$HOST == "node"*
效果是一样的。只需确保通配符在引号外部,如果通配符在引号内部,它将被视为字面意思(即不作为通配符)。
结果:

效果是一样的。只需确保通配符在引号外部,如果通配符在引号内部,它将被视为字面意思(即不作为通配符)。


11

保持简单

word="appel"

if [[ $word = a* ]]
then
  echo "Starts with a"
else
  echo "No match"
fi

8

@OP,对于你的两个问题,你可以使用case/esac:

string="node001"
case "$string" in
  node*) echo "found";;
  * ) echo "no node";;
esac

第二个问题
case "$HOST" in
 node*) echo "ok";;
 user) echo "ok";;
esac

case "$HOST" in
 node*|user) echo "ok";;
esac

Or Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac

请注意,;& 仅适用于 Bash >= 4。 - Dennis Williamson

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