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

In Bash, how can I check if a string begins with some value?

提问人:Tim 提问时间:2/1/2010 最后编辑:Matthias BraunTim 更新时间:7/7/2023 访问量:975662

问:

我想检查字符串是否以“node”开头,例如“node001”。类似的东西

if [ $HOST == node* ]
  then
  echo yes
fi

我怎样才能正确地做到这一点?


我还需要组合表达式来检查是“user1”还是以“node”开头:HOST

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

我怎样才能正确地做到这一点?

String Bash 比较

评论

7赞 hendry 2/1/2010
不要太想组合表达式。有两个单独的条件可能看起来更丑陋,但你可以提供更好的错误消息并使你的脚本更易于调试。此外,我会避免使用 bash 功能。开关是要走的路。

答:

112赞 martin clayton 2/1/2010 #1

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

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

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

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

评论

9赞 Jo So 11/3/2013
你应该双引号${HOST:0:4}
0赞 Peter Mortensen 1/1/2020
@Jo 那么:是什么原因呢?
1赞 Jo So 1/5/2020
@PeterMortensen,试试HOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
0赞 Zim 12/18/2020
或者,双括号:if [[ ${HOST:0:4} = user ]]
0赞 KansaiRobot 3/10/2022
@martin clayton 请发布整个 if 表达式。
364赞 brabster 2/1/2010 #2

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

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

要在正则表达式中匹配,请使用 ,例如,this or that|

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

注意 - 这是“正确的”正则表达式语法。

  • user*均值和 的零次或多次出现,所以 和 将匹配。useruseuserrrr
  • user.*表示值和任何字符的零次或多次出现,因此 将匹配。useruser1userX
  • ^user.*均值与$HOST开头的模式匹配。user.*

如果您不熟悉正则表达式语法,请尝试参考此资源

请注意,Bash 运算符仅在右侧为 UNQUOTED 时执行正则表达式匹配。如果您确实引用了右侧,“可以引用模式的任何部分以强制将其匹配为字符串。即使在进行参数扩展时,也不应引用右侧。=~

评论

0赞 Tim 2/1/2010
谢谢,布拉布斯特!我在原来的帖子中添加了一个新问题,关于如何在 if cluase 中组合表达式。
3赞 CarlosRos 4/27/2017
遗憾的是,公认的答案没有说明正则表达式的语法。
67赞 Trevor Boyd Smith 2/16/2018
仅供参考,Bash =~ 运算符仅在右侧未加引号时执行正则表达式匹配。如果您确实引用了右侧的“可以引用模式的任何部分,以强制将其匹配为字符串。(1.) 确保始终将正则表达式放在右侧不带引号,并且 (2.) 如果您将正则表达式存储在变量中,请确保在进行参数扩展时不要在右侧引用。
1531赞 Mark Rushakoff 2/1/2010 #3

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 的语法很难习惯 (IMO)。if

评论

23赞 JStrahl 3/18/2013
对于正则表达式,您的意思是 [[ $a =~ ^z.* ]] ?
4赞 Niels Bom 6/16/2015
那么 和 之间有功能上的区别吗?换句话说:它们的工作方式不同吗?当你说“$a 等于 z*”时,你具体是什么意思?[[ $a == z* ]][[ $a == "z*" ]]
9赞 Yaza 10/14/2015
如果将“then”放在其自己的行上,则不需要语句分隔符“;”
15赞 lepe 2/6/2017
只是为了完整起见:检查字符串是否以 ... 结尾:[[ $a == *com ]]
7赞 Charles Duffy 10/22/2017
ABS 是一个不幸的参考资料选择——它非常像 W3Schools 的 bash,充满了过时的内容和糟糕的实践示例;至少自 2008 年以来,FreeNode #bash 频道一直试图阻止其使用。有机会重新指向 BashFAQ #31 吗?(我还建议使用 Bash-Hackers 的 wiki,但它现在已经关闭了一段时间)。
65赞 Dennis Williamson 2/1/2010 #4

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

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

编辑:

我已将您的替代项添加到上面的案例陈述中

在编辑后的版本中,括号太多。它应该看起来像这样:

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

评论

0赞 Tim 2/1/2010
谢谢,丹尼斯!我在原来的帖子中添加了一个新问题,关于如何在 if cluase 中组合表达式。
13赞 carlosayam 10/17/2011
“有些人喜欢......”:这个在版本和 shell 之间更具可移植性。
0赞 Jo So 11/6/2014
使用 case 语句,您可以省略变量周围的引号,因为不会发生单词拆分。我知道这是毫无意义和不一致的,但我确实喜欢省略那里的引号,以使其在本地更具视觉吸引力。
0赞 Josiah Yoder 2/19/2017
就我而言,我不得不省略之前的引号:“/*”)不起作用,/*)起作用。(我正在寻找以 / 开头的字符串,即绝对路径)
6赞 just somebody 2/1/2010 #5
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

不起作用,因为所有 、 和 都识别相同的非递归语法。请参见 Bash 手册页上的 CONDITIONAL EXPRESSIONS 部分。[[[test

顺便说一句,SUSv3 说

在早期的提案中,KornShell 派生的条件命令(双括号 [[]])已从 shell 命令语言描述中删除。有人提出反对意见,认为真正的问题是滥用测试命令([),将其放入shell是解决问题的错误方法。相反,适当的文档和新的 shell 保留字 () 就足够了。

需要多个测试操作的测试可以在 shell 级别使用测试命令和 shell 逻辑的单独调用来完成,而不是使用 test 的容易出错的 -o 标志。

你需要这样写,但测试不支持它:

if [ $HOST == user1 -o $HOST == node* ];
then
echo yes
fi

test 使用 = 表示字符串相等,更重要的是它不支持模式匹配。

case / esac对模式匹配有很好的支持:

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

它还有一个额外的好处,那就是它不依赖于 Bash,并且语法是可移植的。从单一的 Unix 规范中,shell 命令语言:

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac

评论

1赞 Dennis Williamson 2/1/2010
[并且是 Bash 内置程序以及外部程序。尝试。testtype -a [
0赞 sdaau 10/23/2011
非常感谢您解释“化合物或”的问题,@just某人 - 正是在寻找这样的东西!干杯!PS 注(与 OP 无关):;...给出 “bash: [: -or: binary operator expected” ; 但是通过。if [ -z $aa -or -z $bb ]if [ -z "$aa" -o -z "$bb" ] ; ...
8赞 ghostdog74 2/1/2010 #6

@OP,对于您的两个问题,您都可以使用案例/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

Bash 4.0的

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

评论

0赞 Dennis Williamson 2/1/2010
请注意,仅在 Bash >= 4 中可用。;&
262赞 Jo So 9/1/2013 #7

我总是尝试坚持使用 POSIX 而不是使用 Bash 扩展,因为脚本的主要要点之一是可移植性(除了连接程序,而不是替换它们)。sh

在 中,有一种简单的方法来检查 “is-prefix” 条件。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

这是优雅的。并不是说我主张将它用于任何严肃的事情——它在现实世界的需求上破坏得太快了(没有 lambdas,所以我们必须使用字符串。但是不可能使用字符串嵌套函数调用,管道是不可能的,等等)sh

评论

26赞 tripleee 9/6/2013
+1 这不仅便携,而且可读性强、惯用且优雅(用于 shell 脚本)。它也自然地延伸到多种模式;case $HOST in user01 | node* ) ...
0赞 Niels Bom 6/16/2015
这种类型的代码格式有名称吗? 我在这里和那里看到它,在我看来它看起来有点皱巴巴的。if case $HOST in node*) true;; *) false;; esac; then
0赞 Jo So 6/17/2015
@NielsBom 我不知道你所说的格式化到底是什么意思,但我的观点是 shell 代码是非常可组合的。Becaues 命令是命令,它们可以进入.caseif ... then
0赞 Niels Bom 6/17/2015
我什至不明白为什么它是可组合的,我对此没有足够的 shell 脚本 :-)我的问题是关于此代码如何使用不匹配的括号和双分号。它看起来与我以前见过的 shell 脚本完全不同,但我可能习惯于看到 bash 脚本而不是 sh 脚本,所以可能就是这样。
0赞 LLFourn 2/5/2016
注意:如果有文字,则应如此,否则可能会给出错误的答案。beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }$1*?
40赞 ozma 11/1/2013 #8

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

此外,我更喜欢用“”来包装字符串以克服空格等。

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

评论

0赞 Anentropic 9/28/2015
谢谢,我也想知道如何匹配以 开头的字符串,看起来这就是答案!blah:
1赞 tripleee 2/17/2021
case $A in "#"*) echo "Skip comment line";; esac既短又便携。
0赞 KansaiRobot 3/10/2022
这是对问题的回答吗?我没有看到任何 sdfs...
0赞 ozma 3/13/2022
KansaiRobot,它演示了如何在现金字符串中使用 *。每个人都应该用他想要比较的字符串来回复“SDFS”
46赞 dhke 5/6/2016 #9

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

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

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

${var#expr}从中剥离匹配的最小前缀并返回该前缀。因此,如果以 () 开头,则 () 与 相同。expr${var}${host}usernode${host#user}${host#node}${host}

expr允许 fnmatch() 通配符,因此和朋友也可以工作。${host#node??}

评论

3赞 x-yuri 10/25/2018
我认为抨击可能是必要的,因为它比 .因此,假设您控制执行脚本的环境(以 的最新版本为目标),则最好选择前者。[[ $host == user* ]][ "${host#user}" != "${host}" ]bash
3赞 dhke 10/25/2018
@x-yuri:坦率地说,我只是把它打包成一个函数,再也不看它了。has_prefix()
16赞 MrPotatoHead 8/6/2018 #10

在 Mark Rushakoff 的最高排名答案中添加更多语法细节。

表达式

$HOST == node*

也可以写成

$HOST == "node"*

效果是一样的。只需确保通配符位于引用的文本之外即可。如果通配符在引号,则按字面意思解释(即不作为通配符)。

3赞 Mike Slinn 4/28/2019 #11

我调整了@markrushakoff的答案,使其成为一个可调用的函数:

function yesNo {
  # Prompts user with $1, returns true if response starts with y or Y or is empty string
  read -e -p "
$1 [Y/n] " YN

  [[ "$YN" == y* || "$YN" == Y* || "$YN" == "" ]]
}

像这样使用它:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] Y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] yes
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n]
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] ddddd
false

下面是一个更复杂的版本,它提供了指定的默认值:

function toLowerCase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function yesNo {
  # $1: user prompt
  # $2: default value (assumed to be Y if not specified)
  # Prompts user with $1, using default value of $2, returns true if response starts with y or Y or is empty string

  local DEFAULT=yes
  if [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fi
  if [[ "$DEFAULT" == y* ]]; then
    local PROMPT="[Y/n]"
  else
    local PROMPT="[y/N]"
  fi
  read -e -p "
$1 $PROMPT " YN

  YN="$( toLowerCase "$YN" )"
  { [ "$YN" == "" ] && [[ "$PROMPT" = *Y* ]]; } || [[ "$YN" = y* ]]
}

像这样使用它:

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N]
false

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N] y
true

$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false
5赞 Ciro Santilli OurBigBook.com 6/17/2019 #12

grep

忘记性能,这是 POSIX,看起来比解决方案更好:case

mystr="abcd"
if printf '%s' "$mystr" | grep -Eq '^ab'; then
  echo matches
fi

解释:

11赞 Thomas Van Holder 7/5/2022 #13

保持简单

word="appel"

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