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

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

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

1
2
3
4
if [ $HOST == user* ]  
  then  
  echo yes  
fi

我怎样才能正确地做?

我还需要组合表达式来检查主机是"user1"还是以"node"开头

1
2
3
4
5
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];  
then  
echo yes
fi

> > > -bash: [: too many arguments

如何正确操作?


《高级bash脚本编写指南》中的这段代码指出:

1
2
3
4
5
# 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).

所以你几乎正确了;你需要双括号,而不是单括号。

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

1
2
3
4
5
6
7
8
9
10
11
HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

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

哪个会回响

1
2
yes1
yes2

bash的if语法很难习惯(imo)。


如果您使用的是最新的bash(v3+),我建议使用bash regex比较运算符=~,即

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

为了匹配regex中的this or that,使用|,即

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

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

  • user*表示user的零个或多个出现,因此useuserrrr将匹配。
  • user.*表示user和任何字符的零个或多个出现,因此user1userX将匹配。
  • ^user.*表示匹配$host开头的模式user.*

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

注意-如果你把每个新问题作为一个新问题来问,它会使stackoverflow更整洁,更有用。您可以始终包含一个指向上一个问题的链接,以供参考。


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

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

1
2
3
case $HOST in node*)
    your code here
esac

考虑到sh的古老、神秘和粗糙(bash不是解决方法:它更复杂、更不一致、更不可移植),我想指出一个非常好的功能方面:虽然像case这样的语法元素是内置的,但生成的构造与其他任何工作都没有什么不同。它们可以用相同的方式组成:

1
2
3
if case $HOST in node*) true;; *) false;; esac; then
    your code here
fi

甚至更短

1
2
3
if case $HOST in node*) ;; *) false;; esac; then
    your code here
fi

甚至更短(只是将!作为一个语言元素呈现——但现在这是一种糟糕的风格)

1
2
3
if ! case $HOST in node*) false;; esac; then
    your code here
fi

如果您喜欢显式,请构建自己的语言元素:

1
beginswith() { case $2 in"$1"*) true;; *) false;; esac; }

这不是很好吗?

1
2
3
if beginswith node"$HOST"; then
    your code here
fi

由于sh基本上只是作业和字符串列表(以及内部进程,其中的作业是由这些进程组成的),我们现在甚至可以进行一些简单的函数式编程:

1
2
3
4
5
6
7
8
9
10
11
12
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
if [ ${HOST:0:4} = user ]

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

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


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

1
2
3
4
5
6
case"$HOST" in
    user1|node*)
            echo"yes";;
        *)
            echo"no";;
esac

编辑:

我已经在上面的案例陈述中添加了你的替代者

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

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


因为在bash中有意义,所以我得出了以下解决方案。此外,我更喜欢用"克服空格等"来包装字符串。

1
2
3
4
A="#sdfs"
if [["$A" =="#"* ]];then
    echo"skip comment line"
fi


虽然我在这里发现大多数答案都是正确的,但其中许多都包含不必要的害羞。posix参数扩展为您提供了所需的一切:

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

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

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

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


@op,对于这两个问题,您都可以使用case/esac

1
2
3
4
5
string="node001"
case"$string" in
  node*) echo"found";;
  * ) echo"no node";;
esac

第二个问题

1
2
3
4
5
6
7
8
case"$HOST" in
 node*) echo"ok";;
 user) echo"ok";;
esac

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

或巴什4

1
2
3
4
case"$HOST" in
 user) ;&
 node*) echo"ok";;
esac


在马克·拉沙科夫的最高级答案中增加了一点语法细节。

表达式

1
$HOST == node*

也可以写为

1
$HOST =="node"*

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


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

不起作用,因为所有的[、[和测试都能识别相同的非递归语法。请参见bash手册页中的条件表达式一节。

作为旁白,SUSV3说

The KornShell-derived conditional command (double bracket [[]]) was removed from the shell command language description in an early proposal. Objections were raised that the real problem is misuse of the test command ([), and putting it into the shell is the wrong way to fix the problem. Instead, proper documentation and a new shell reserved word (!) are sufficient.

Tests that require multiple test operations can be done at the shell level using individual invocations of the test command and shell logicals, rather than using the error-prone -o flag of test.

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

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

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

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

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

它还有一个额外的好处,即它不依赖于bash,语法是可移植的。从单个UNIX规范中,shell命令语言:

1
2
3
4
5
case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac


你能做的另一件事是用cat把你正在回响的声音和管道传给inline cut -c 1-1