【问题标题】:Case insensitive comparison of strings in shell scriptshell脚本中字符串的大小写不敏感比较
【发布时间】:2009-11-13 11:31:51
【问题描述】:

== 运算符用于比较 shell 脚本中的两个字符串。但是,我想比较两个字符串忽略大小写,怎么办?有什么标准的命令吗?

【问题讨论】:

  • 快速说明:不,== 不是 shell 中的标准字符串比较运算符;唯一符合 POSIX 标准的比较运算符是 =

标签: string shell compare case-insensitive


【解决方案1】:

在 Bash 中,您可以使用参数扩展将字符串修改为全部小写/大写:

var1=TesT
var2=tEst

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

【讨论】:

  • 至少是一个不暗示shopt选项的回复。因此,您可以比较两个忽略大小写的字符串,并在同一个测试中,将另外两个字符串与大小写进行比较。谢谢
  • 这是 Bash 4 中的新功能吗?至少在 Bash 3.2.51(在 OS X 10.9 中使用)它不起作用 - 第一个 echo 语句导致:-bash: ${var1,,}: bad substitution
  • 这种不区分大小写的比较实现容易出现本地化问题(如土耳其语 I 问题)。我不知道shopt -s nocasematch 是如何实现的,但通常这种“语言级”解决方案可以正确处理它。
  • 我和@FelixRabe 在一起;在 macOS 10.13.6 上,它附带 Bash 3.2.57 并给出相同的错误
  • ,,^^bash 4.4.20 中工作,但在 zsh 5.4.2 (ubuntu 18.04) 中均不工作
【解决方案2】:

所有这些答案都忽略了最简单快捷的方法(只要你有 Bash 4):

if [ "${var1,,}" = "${var2,,}" ]; then
  echo ":)"
fi

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

【讨论】:

  • 这仅适用于 Bash 4 或更高版本(例如,不适用于 Mac OS X 10.11)
  • @d4Rk 这就是为什么我的回答的第一句话说“只要你有 Bash 4”。话虽如此,在撰写此评论时,Bash 4 已经发布了七年多,而我自己的 OS X 安装也已经使用了差不多这么长时间。
  • @Riot 抱歉,一开始没有注意到这一点。我知道你可以在 OS X 上安装任何你想要的 bash,但默认是 3.2,因为我的脚本也必须在不同的 Mac 上运行,这对我来说不是一个真正的选择。
  • @d4Rk 这是可以理解的——如果你真的需要保证可移植性,我建议你坚持使用 POSIX shell 标准,因为在某些情况下你根本无法保证找到任何版本的 bash。
  • 对此的附加评论,我花了一段时间才弄清楚。你必须在相等之间有一个空格。
【解决方案3】:

如果你有 bash

str1="MATCH"
str2="match"
shopt -s nocasematch
case "$str1" in
 $str2 ) echo "match";;
 *) echo "no match";;
esac

否则,您应该告诉我们您使用的是什么外壳。

替代方案,使用 awk

str1="MATCH"
str2="match"
awk -vs1="$str1" -vs2="$str2" 'BEGIN {
  if ( tolower(s1) == tolower(s2) ){
    print "match"
  }
}'

【讨论】:

  • 对于任何使用 if 语句比较字符串的人,shopt 方法要求您使用双括号 [[ ]] 条件形式而不是单括号 [ ] 形式。另见:gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
  • 该问题表明== 用于比较两个字符串,但响应显示使用case 语句进行不区分大小写的比较。令人欣慰的是,shopt 解决方案还可以不区分大小写地使用 ===~ 和其他字符串比较运算符。
  • 在比较完成后执行shopt -u nocasematch 以恢复到 bash 的默认值可能是明智的。
  • 最好保存和恢复nocasematch设置。用SHELLNOCASEMATCH=`shopt -p nocasematch` 抓取它,然后用shopt -s nocasematch 更改它,一旦完成,用$SHELLNOCASEMATCH 恢复它
  • 更好的是:SHELLNOCASEMATCH=$(shopt -p nocasematch; true),因为如果未设置该选项,shopt -p 将以代码 1 退出,如果set -e 生效,这可能会导致脚本中止。
【解决方案4】:

保存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) 失败时阻止 shell 退出(因为根本没有设置 nocasematch)。

【讨论】:

  • 很好,因为case 声明(包括ghostdog 的回答中的那些)总是会让我的皮肤爬行
  • @Seldom'Where's Monica'Needy,为什么? case 符合 POSIX 标准; [[ ]] 是非标准扩展。从可移植性的角度来看,case 是可行的方法。
【解决方案5】:

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

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

【讨论】:

  • 我在基于 alpine 的 docker 化应用程序中使用了tr 方法(通过busybox 提供sh)。谢谢。
  • test "$(...)" = "$(...)" -- 对于所有可能的字符串的正确操作,双引号不是可选的。
【解决方案6】:

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

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

【讨论】:

  • 就性能而言,这比启动 awk 或任何其他进程要好得多。
  • 在bash中,可以使用-l或-u来设置属性。
【解决方案7】:

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

str1="MATCH"
str2="match"

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

【讨论】:

  • 改用if fgrep -qix -- "$str1" &lt;&lt;&lt;"$str2"; then会更好。
【解决方案8】:

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"

【讨论】:

  • 为什么第二行有 globbing flags 比较 str1 以匹配?
  • @Timo 我相信这只是一个例子,表明它也适用于文字字符串比较。答案已更新
【解决方案9】:

我遇到了这个很棒的博客/教程/关于dealing with case sensitive pattern 的任何内容。以下三种方法通过实例进行详细说明:

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

【讨论】:

  • 第一个非常适合我的情况,感谢您的详细回答!
【解决方案10】:

这是我使用 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 中会有一个文件名列表,即使它不包含这样的列表之前。
【解决方案11】:

grep 有一个 -i 标志,表示不区分大小写,因此请它告诉您 var2 是否在 var1 中。

var1=match 
var2=MATCH 
if echo $var1 | grep -i "^${var2}$" > /dev/null ; then
    echo "MATCH"
fi

【讨论】:

  • 如果 var2 中有任何正则表达式特殊字符将不起作用。
【解决方案12】:

在类 Unix 操作系统上,test 命令检查文件类型并比较值。

str1="MATCH"
str2="match"

if test $str1 =  $str2
  then
  echo "equal yes"
else
  echo "equal not"
fi

在类 Unix 操作系统上,test 命令检查文件类型并比较值。

这样很简单。

几行代码

【讨论】:

  • 它应该是test "$str1" = "$str2" -- 引号对于可靠的正确操作不是可选的。
【解决方案13】:

shopt -s nocaseglob

【讨论】:

    【解决方案14】:

    “tr”实用程序(翻译字符)始终存在于所有 Unix/Linux 机器上,并且是轻量级的。

    这是一个可用于处理不区分大小写比较的函数。由于 'tr' 的确切位置可能不同,我们首先探测其可能的位置并将正确的位置存储在适当命名为“BIN_TR”的环境变量中。

    declare BIN_TR=$( ls /bin/tr /usr/bin/tr 2>/dev/null | head -1 );
    

    然后在函数声明中使用。

    toLowerCase() {
      echo "$*" | $BIN_TR '[:upper:]' '[:lower:]'
    }
    

    使用“tr”的解决方案预计在不同的操作系统和操作系统设置变体之间具有高度可移植性。虽然 'awk' 也很有可能,但与 'awk' 相比,'tr' 实用程序很小,因此使用 'tr' 的函数可能更轻。

    【讨论】:

    • 你为什么要以这种方式使用... | $BIN_TR 而不仅仅是... | tr? shell 本身会自动进行 PATH 查找并缓存该查找——它的内置查找比使用 ls 的 hack 更有效。
    • 此外,一些发行版根本不使用/bin/usr/bin。我在 NixOS 机器上写这个;我的tr 副本来自/run/current-system/sw/bin/tr,它是/nix/store/cc4nnlspm4pwmp5rvjl6wpy9nyzcsbnr-coreutils-8.31/bin/tr 的符号链接。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-22
    • 1970-01-01
    • 2010-11-26
    • 2011-02-06
    • 2019-04-15
    相关资源
    最近更新 更多