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

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

if [ $HOST == user* ]thenecho yesfi

我怎样才能正确地做呢?


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

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];thenecho yesfi
> > > -bash: [: too many arguments

我怎样才能正确地做呢?

895147 次浏览

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

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

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

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

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

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

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

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

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

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

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

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

高级Bash脚本指南上的这个片段说:

# 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=user1if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;thenecho yes1fi
HOST=node001if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;thenecho yes2fi

会有回声

yes1yes2

Bash的if语法很难习惯(IMO)。

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

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

编辑:

我已经把你的候补成员加到上面的案例陈述里了

在您编辑的版本中,您有太多括号。它应该如下所示:

if [[ $HOST == user1 || $HOST == node* ]];
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];thenecho yesfi

不起作用,因为所有[[[test都识别相同的非递归语法。请参阅Bash手册页上的第3节。

顺便说一句,SUSv3说

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

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

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

if [ $HOST == user1 -o $HOST == node* ];thenecho yesfi

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

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

case $HOST inuser1|node*) echo yes ;;esac

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

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

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

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

第二个问题

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

Bash 4.0

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

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

sh中,有一种简单的方法来检查“is前缀”条件。

case $HOST in node*)# Your code hereesac

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

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

或者更短

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

或者甚至更短(只是为了将!呈现为语言元素-但这现在是糟糕的风格)

if ! case $HOST in node*) false;; esac; then# Your code herefi

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

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

这不是很不错吗?

if beginswith node "$HOST"; then# Your code herefi

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

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }
all() {test=$1; shiftfor i in "$@"; do$test "$i" || returndone}
all "beginswith x" x xy xyz ; checkresult  # Prints TRUEall "beginswith x" x xy abc ; checkresult  # Prints FALSE

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

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

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

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

虽然我发现这里的大多数答案都是正确的,但其中许多都包含了不必要的巴什主义。

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

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

${var#expr}${var}中去掉与expr匹配的最小前缀并返回它。因此,如果${host}确实没有usernode)开头,则${host#user}${host#node})与${host}相同。

expr允许#1通配符,因此${host#node??}和朋友也有效。

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

的表达

$HOST == node*

也可以写成

$HOST == "node"*

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

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

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

像这样使用它:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi
asfd [Y/n] ytrue
$ if yesNo "asfd"; then echo "true"; else echo "false"; fi
asfd [Y/n] Ytrue
$ if yesNo "asfd"; then echo "true"; else echo "false"; fi
asfd [Y/n] yestrue
$ 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] nfalse
$ if yesNo "asfd"; then echo "true"; else echo "false"; fi
asfd [Y/n] dddddfalse

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

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=yesif [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fiif [[ "$DEFAULT" == y* ]]; thenlocal PROMPT="[Y/n]"elselocal PROMPT="[y/N]"firead -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] ytrue
$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi
asfd [Y/n] nfalse

grep

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

mystr="abcd"if printf '%s' "$mystr" | grep -Eq '^ab'; thenecho matchesfi

说明:

保持简单

word="appel"
if [[ $word = a* ]]thenecho "Starts with a"elseecho "No match"fi