在shell脚本中进行字符串大小写不敏感的比较

209

==操作符用于在Shell脚本中比较两个字符串。然而,我希望比较两个字符串时忽略大小写,怎么办?有没有标准命令可以做到这一点?

可以使用strcasecmp函数来比较两个字符串而忽略大小写。以下是使用该函数的示例:

if [ strcasecmp "$str1" "$str2" -eq 0 ]
then
    echo "Strings are equal ignoring case"
else
    echo "Strings are not equal ignoring case"
fi

要注意的是,此函数只能在Bash shell和Korn shell中使用。


4
快速提示:在shell中,==不是标准的字符串比较运算符;唯一POSIX标准化的比较运算符是= - Charles Duffy
14个回答

229
在Bash中,你可以使用参数扩展来将字符串修改为全小写/全大写:使用${var,,}来转换为全小写,使用${var^^}来转换为全大写。
var1=TesT
var2=tEst

echo ${var1,,} ${var2,,}
echo ${var1^^} ${var2^^}

18
请问您需要的翻译是:至少要一个回复,不含商店选项。这样您可以比较两个字符串,忽略大小写,并在同一测试中比较另外两个带大小写的字符串。谢谢。 - jehon
54
Bash 4中有这个功能吗?至少在Bash 3.2.51(用于OS X 10.9)中,它不起作用 - 第一个“echo”语句会导致以下结果:“-bash:$ {var1,,}:错误的替换”。 - Felix Rabe
1
这种不区分大小写的比较实现容易出现本地化问题(例如土耳其语中的 I 问题)。我不知道 shopt -s nocasematch 是如何实现的,但通常这种“语言级别”的解决方案可以正确处理它。 - Ohad Schneider
我和@FelixRabe一样,在macOS 10.13.6上,它附带了Bash 3.2.57,并且会出现相同的错误。 - Ky -
./anothertest.sh: 第4行: ${var1,,}: 错误的替换 ./anothertest.sh: 第5行: ${var1^^}: 错误的替换 - Starlton
6
bash 4.4.20中,,,^^都可以使用,但在zsh 5.4.2(ubuntu 18.04)中都不起作用。 - transang

183
所有答案都忽略了最简单和最快的方法(只要您有Bash 4):
if [ "${var1,,}" = "${var2,,}" ]; then
  echo ":)"
fi

你所做的只是将这两个字符串转换为小写并比较结果。


11
此功能仅适用于 Bash 4 或更新版本(例如,在 Mac OS X 10.11 上不可用)。 - d4Rk
16
正因为此,我回答的第一句话说:“只要你有Bash 4”。话虽如此,在我写这条评论时,Bash 4已经发布超过七年了,我的OS X安装也将近有那么长时间。 - Riot
@Riot 抱歉,一开始没有注意到这个问题。我知道你可以在OS X上安装任何bash版本,但默认的版本是3.2,而且由于我的脚本必须在不同的Mac上运行,所以这对我来说并不是一个真正的选择。 - d4Rk
3
@d4Rk 这很容易理解 - 如果你真的需要保证可移植性,我建议坚持使用 POSIX shell 标准,因为在某些情况下你不能保证找到任何版本的 bash。 - Riot
对于这个问题,我想再补充一点,因为我花了一段时间才弄明白。你必须在等号之间加上一个空格。 - Michael Rountree

97

如果您有Bash

str1="MATCH"
str2="match"
shopt -s nocasematch
case "$str1" in
 $str2 ) echo "match";;
 *) echo "no match";;
esac
否则,请告诉我们您使用的是什么Shell。
另一种方法是使用awk。
str1="MATCH"
str2="match"
awk -vs1="$str1" -vs2="$str2" 'BEGIN {
  if ( tolower(s1) == tolower(s2) ){
    print "match"
  }
}'

42
对于使用if语句比对字符串的人,使用shopt方法需要用到双中括号[[ ]]形式的条件语句,而不是单中括号[ ]形式。参见:http://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html。 - indiv
4
这个问题表明==用于比较两个字符串,但是回答展示了使用case语句进行大小写不敏感的比较。令人放心的是,shopt解决方案也可以使===~和其他字符串比较运算符大小写不敏感。 - taranaki
13
在比较完成后执行shopt -u nocasematch可能是明智的选择,以恢复bash的默认设置。 - Ohad Schneider
11
最好保存和恢复 nocasematch 设置。使用 SHELLNOCASEMATCH=\shopt -p nocasematch`获取它,然后使用shopt -s nocasematch进行更改,完成后使用$SHELLNOCASEMATCH` 进行恢复。 - Urhixidur
7
更好的写法是:SHELLNOCASEMATCH=$(shopt -p nocasematch; true),因为 shopt -p 如果未设置该选项会返回退出码1,如果此时生效了 set -e 选项的话,这会导致脚本停止执行。 - We Are All Monica
为了在bash脚本中使用awk的比较,我会倾向于这样做:if awk -vs1="$str1" -vs2="$str2" 'BEGIN {exit (tolower(s1)!=tolower(s2))}'; then echo "'$str1' and '$str2' match"; fi - mwfearnley

52

保存 nocasematch 的状态(以防其他函数依赖于其被禁用):

local orig_nocasematch=$(shopt -p nocasematch; true)
shopt -s nocasematch

[[ "foo" == "Foo" ]] && echo "match" || echo "notmatch"

$orig_nocasematch
注意:local 命令只需在函数内部使用。
另外,; true 部分可防止当 set -e 打开,并且 $(shopt -p nocasematch) 失败(因为未设置 nocasematch)时,shell 退出。

5
很好,因为“case”语句(包括ghostdog回答中的那些)总是让我毛骨悚然。 - Seldom 'Where's Monica' Needy
1
@Seldom'Where'sMonica'Needy,为什么?case符合POSIX标准;[[ ]]是一种非标准扩展。从可移植性的角度来看,case是更好的选择。 - Charles Duffy
顺便提一下:使用 local 可以抑制 $(shopt -p nocasematch) 的失败,因此在这种情况下不需要 true - Maxim Kholyavkin

25

一种方法是将两个字符串转换为大写或小写:

test $(echo "string" | /bin/tr '[:upper:]' '[:lower:]') = $(echo "String" | /bin/tr '[:upper:]' '[:lower:]') && echo same || echo different

另一种方法是使用grep:

echo "string" | grep -qi '^String$' && echo same || echo different

1
我在基于alpine的docker应用程序中使用了tr方法(该方法通过busybox提供sh)。谢谢。 - MXWest
test "$(...)" = "$(...)" -- 双引号对于与所有可能的字符串正确运行是必需的。 - Charles Duffy

10
对于zsh,语法略有不同,但仍比大多数答案要短:
> str1='mAtCh'
> str2='MaTcH'
> [[ "$str1:u" = "$str2:u" ]] && echo 'Strings Match!'
Strings Match!
>

这将在比较之前将两个字符串都转换为大写字母。
另一种方法利用zsh的globbing flags,通过使用 i glob标志,我们可以直接利用不区分大小写匹配。
setopt extendedglob
[[ $str1 = (#i)$str2 ]] && echo "Match success"

# this example compares the variable with a literal string 'match'
[[ $str1 = (#i)match ]] && echo "Match success"

10
我发现了一篇关于处理大小写敏感模式的很棒的博客/教程/其他内容。下面详细解释了以下三种方法,并附有示例:
1. 使用tr命令将模式转换为小写
opt=$( tr '[:upper:]' '[:lower:]' <<<"$1" )
case $opt in
        sql)
                echo "Running mysql backup using mysqldump tool..."
                ;;
        sync)
                echo "Running backup using rsync tool..."
                ;;
        tar)
                echo "Running tape backup using tar tool..."
                ;;
        *)
                echo "Other options"
                ;;
esac

2. 使用谨慎的通配符与大小写模式匹配

opt=$1
case $opt in
        [Ss][Qq][Ll])
                echo "Running mysql backup using mysqldump tool..."
                ;;
        [Ss][Yy][Nn][Cc])
                echo "Running backup using rsync tool..."
                ;;
        [Tt][Aa][Rr])
                echo "Running tape backup using tar tool..."
                ;;
        *)
                echo "Other option"
                ;;
esac

3. 打开 nocasematch

opt=$1
shopt -s nocasematch
case $opt in
        sql)
                echo "Running mysql backup using mysqldump tool..."
                ;;
        sync)
                echo "Running backup using rsync tool..."
                ;;
        tar)
                echo "Running tape backup using tar tool..."
                ;;
        *)
                echo "Other option"
                ;;
esac

shopt -u nocasematch

第一个答案非常适合我的情况,感谢您提供的详细解答! - benCat

7

如果你想要进行不区分大小写的行比较,使用fgrep非常容易:

str1="MATCH"
str2="match"

if [[ $(fgrep -ix $str1 <<< $str2) ]]; then
    echo "case-insensitive match";
fi

最好使用if fgrep -qix -- "$str1" <<<"$str2"; then - user3285991

7

对于korn shell,我使用typeset内置命令(-l用于小写,-u用于大写)。

var=True
typeset -l var
if [[ $var == "true" ]]; then
    print "match"
fi

2
这个在性能方面比起启动awk或其他进程要好得多。 - Alex
1
在Bash中,可以使用declare -l或-u来设置属性。 - Bharat

4

这是我使用 tr 命令的解决方案:

var1=match
var2=MATCH
var1=`echo $var1 | tr '[A-Z]' '[a-z]'`
var2=`echo $var2 | tr '[A-Z]' '[a-z]'`
if [ "$var1" = "$var2" ] ; then
  echo "MATCH"
fi

echo $var 需要加引号 -- echo "$var",或者如果 var1='hello * world',即使在操作之前它不包含这样的列表,你也会在你的 var1 中得到一个文件名列表。 - Charles Duffy

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