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

Posted

技术标签:

【中文标题】在 Bash 中,如何检查字符串是不是以某个值开头?【英文标题】:In Bash, how can I check if a string begins with some value?在 Bash 中,如何检查字符串是否以某个值开头? 【发布时间】:2011-01-11 10:56:43 【问题描述】:

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

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

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


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

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

> > > -bash: [: too many arguments

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

【问题讨论】:

不要太想组合表达式。有两个单独的条件可能看起来更难看,尽管您可以提供更好的错误消息并使您的脚本更易于调试。我也会避免使用 bash 功能。开关是要走的路。 【参考方案1】:

如果您使用的是最新版本的 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.*

如果您不熟悉正则表达式语法,请尝试参考this resource。

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

【讨论】:

谢谢,布拉布斯特!我在原帖中添加了一个关于如何在 if cluase 中组合表达式的新问题。 遗憾的是,接受的答案没有说明正则表达式的语法。 仅供参考,Bash =~ 运算符仅在右侧未引用时才进行正则表达式匹配。如果您确实引用了右侧“可能会引用模式的任何部分以强制将其作为字符串匹配。” (1.) 确保始终将正则表达式放在未引用的右侧(2.) 如果您将正则表达式存储在变量中,请确保在进行参数扩展时不要引用右侧。【参考方案2】:

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

sh 中,有一种简单的方法可以检查“is-prefix”条件。

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

考虑到 sh 有多古老、晦涩难懂(而且 Bash 不是灵丹妙药:它更复杂、更不一致且更不便携),我想指出一个非常好的功能方面:虽然一些语法元素,如 @ 987654324@ 是内置的,生成的构造与任何其他工作没有什么不同。它们可以以相同的方式组合:

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,所以我们必须使用字符串。但是用字符串嵌套函数调用是不可能的,管道是不可能的,等等.)

【讨论】:

+1 这不仅是可移植的,它也是可读的、惯用的和优雅的(对于 shell 脚本)。它还自然地扩展到多种模式; case $HOST in user01 | node* ) ... 这种代码格式有名称吗? if case $HOST in node*) true;; *) false;; esac; then我到处都看到它,在我看来它看起来有点皱巴巴的。 @NielsBom 我不知道你所说的格式化到底是什么意思,但我的意思是 shell 代码非常可组合。因为case命令是命令,它们可以进入if ... then 我什至不明白为什么它是可组合的,我不了解足够的 shell 脚本 :-) 我的问题是关于这段代码如何使用不匹配的括号和双分号。它看起来不像我以前见过的 shell 脚本,但我可能习惯于看到 bash 脚本而不是 sh 脚本,所以可能就是这样。 注意:应该是beginswith() case "$2" in "$1"*) true;; *) false;; esac; 否则如果$1 有文字*? 它可能会给出错误的答案。【参考方案3】:

我调整了@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

【讨论】:

【参考方案4】:

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

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

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

$var#expr$var 中去除匹配expr 的最小前缀并返回它。因此,如果$host不是user (node) 开头,$host#user ($host#node) 与$host 相同。

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

【讨论】:

我认为 bashism [[ $host == user* ]] 可能是必要的,因为它比 [ "$host#user" != "$host" ] 更具可读性。因此,如果您可以控制执行脚本的环境(针对bash 的最新版本),则最好使用前者。 @x-yuri 坦率地说,我只是将它打包到 has_prefix() 函数中,然后再也不看它了。【参考方案5】:

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

此外,我更喜欢用 "" 打包字符串以克服空格等。

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

【讨论】:

这正是我所需要的。谢谢! 谢谢,我也想知道如何匹配以blah: 开头的字符串,看来这就是答案! case $A in "#"*) echo "Skip comment line";; esac 更短更便携。【参考方案6】:

@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

或者 Bash 4.0

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

【讨论】:

请注意,;& 仅在 Bash >= 4 中可用。【参考方案7】:
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

不起作用,因为所有[[[test 都识别相同的非递归语法。请参阅 Bash 手册页上的 条件表达式 部分。

顺便说一句,SUSv3 说

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

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

你需要这样写,但 test 不支持:

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

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

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

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

它还有一个额外的好处是它不依赖于 Bash,并且语法是可移植的。来自Single Unix SpecificationShell 命令语言

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

【讨论】:

[test 是 Bash 内置程序以及外部程序。试试type -a [ 非常感谢您解释“复合或”的问题,@just someone - 正是在寻找类似的东西!干杯! PS注意(与OP无关):if [ -z $aa -or -z $bb ]; ...给出 "bash: [: -or: binary operator expected" ;但是if [ -z "$aa" -o -z "$bb" ] ; ... 通过了。【参考方案8】:

grep

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

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

解释:

printf '%s' 防止 printf 扩展反斜杠转义:Bash printf literal verbatim string grep -q 防止匹配到标准输出的回显:How to check if a file contains a specific string using Bash grep -E 启用扩展正则表达式,我们需要 ^

【讨论】:

【参考方案9】:

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

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

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

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

【讨论】:

你应该双引号 $HOST:0:4 @Jo So:是什么原因? @PeterMortensen,试试HOST='a b'; if [ $HOST:0:4 = user ] ; then echo YES ; fi 或者,双括号:if [[ $HOST:0:4 = user ]]【参考方案10】:

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

表达式

$HOST == node*

也可以写成

$HOST == "node"*

效果是一样的。只要确保通配符在引用的文本之外。如果通配符在 inside 引号内,它将按字面意思解释(即不作为通配符)。

【讨论】:

【参考方案11】:

Advanced Bash Scripting Guide 上的这个 sn-p 说:

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

【讨论】:

对于正则表达式,您的意思是 [[ $a =~ ^z.* ]] 吗? 那么[[ $a == z* ]][[ $a == "z*" ]] 之间在功能上有区别吗?换句话说:它们的工作方式不同吗?当你说“$a 等于 z*”时,你具体是什么意思? 您不需要语句分隔符“;”如果你把“那么”放在自己的一行 为了完整性:检查字符串是否以 ... 结尾:[[ $a == *com ]] ABS 是一个不幸的参考选择——它非常像 bash 的 W3Schools,充满了过时的内容和不良做法的例子; freenode #bash 频道一直试图阻止其使用at least since 2008。有没有机会重新指向BashFAQ #31? (我也建议使用 Bash-Hackers 的 wiki,但它现在已经关闭了一段时间)。【参考方案12】:

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

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

编辑:

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

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

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

【讨论】:

谢谢,丹尼斯!我在原始帖子中添加了一个关于如何在 if cluase 中组合表达式的新问题。 "some people like..." :这个更易于跨版本和外壳。 使用 case 语句,您可以省略变量周围的引号,因为不会发生分词。我知道这毫无意义且前后矛盾,但我确实喜欢在此处省略引号,以使其在本地更具视觉吸引力。 而在我的例子中,我不得不在 ): "/*") 没有用,/*) 之前去掉引号。 (我正在寻找以 / 开头的字符串,即绝对路径)

以上是关于在 Bash 中,如何检查字符串是不是以某个值开头?的主要内容,如果未能解决你的问题,请参考以下文章

检查并验证以某个值开头的某个数字是不是可用

如何在 Swift 中检查字符串以(前缀)开头或以(后缀)结尾

如何检查 CComBSTR 是不是以某个前缀开头?

如何在C中检查十六进制数(%x)是否以某个数字开头?

检查字符串是不是以正则表达式数字开头

如何检查单词是不是以给定字符开头?