微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

比较 Bash 中按字母顺序排列的字符串、测试与双括号语法

如何解决比较 Bash 中按字母顺序排列的字符串、测试与双括号语法

我正在处理一个 Bash 脚本项目,在该项目中,如果两个文件内容相同,我需要删除它们之一。我应该删除一个字母排序,在示例中,我的教授提供了我的教授,Apple.dat是删除的选择是Apple.dat和Apple.dat。

if [[ "apple" > "Apple" ]]; then
    echo apple
else
    echo Apple
fi

打印苹果

echo $(echo -e "Apple\napple" | sort | tail -n1)

打印苹果

a 的 ASCII 值是 97,A 是 65,为什么测试说 A 更大?

奇怪的是,我用旧的语法得到了相反的结果:

if [ "apple" \> "Apple" ]; then
    echo apple
else
    echo Apple
fi

打印苹果

如果我们尝试在 [[ ]] 语法中使用 \>,这是一个语法错误

我们如何针对双括号语法更正此问题?我已经在学校的 Debian 服务器、我的本地机器和我的 Digital Ocean Droplet 服务器上对此进行了测试。在我本地的 Ubuntu 20.04 和学校服务器上,我得到了上述输出。有趣的是,在我的 Digital Ocean Droplet(Ubuntu 20.04 服务器)上,我得到了带有双括号和单括号语法的“apple”。我们可以使用语法、双括号或单括号实际测试调用,但是我更喜欢使用较新的双括号语法,并且宁愿学习如何使这项工作而不是将我大部分完成的脚本转换为较旧的更符合 POSIX 的语法.

解决方法

提示:

$ (LC_COLLATE=C; if [ "apple" \> "Apple" ]; then echo apple; else echo Apple; fi)
apple
$ (LC_COLLATE=en_US; if [ "apple" \> "Apple" ]; then echo apple; else echo Apple; fi)
apple

但是:

$ (LC_COLLATE=C; if [[ "apple" > "Apple" ]]; then echo apple; else echo Apple; fi)
apple
$ (LC_COLLATE=en_US; if [[ "apple" > "Apple" ]]; then echo apple; else echo Apple; fi)
Apple

不同之处在于 Bash 特定测试 [[ ]] 使用语言环境整理规则来比较字符串。而 POSIX 测试 [ ] 使用 ASCII 值。

来自 bash 手册页:

[[ 一起使用时,<> 运算符按字典顺序使用当前语言环境进行排序。

当与 test[ 一起使用时,<> 运算符按字典顺序使用 ASCII 排序

,

我已经想出了我自己的解决方案,但是我必须首先感谢@GordonDavisson 和@LéaGris 的帮助以及我从他们那里学到的东西,因为这对我来说是无价的。

无论使用计算机语言环境还是人类语言环境,如果按字母顺序排列,apple 在 Apple 之后,那么它也在 Banana 之后;如果 Banana 在 apple 之后,则 Apple 在 apple 之后。所以我想出了以下几点:

# A function which sorts two words alphabetically with lower case coming after upper case.
# The last word in the sort will be printed twice to demonstrate that this works for both
# the POSIX compliant single bracket test call and the newer double bracket condition
# syntax.
# arg 1: One of two words to sort
# arg 2: One of two words to sort
# Return: 0 upon completion,1 if incorrect number of args is given
sort_alphabetically() {
    [ $# -ne 2 ] && return 1

    word_1_val=0
    word_2_val=0

    while read -n1 letter; do
        (( word_1_val += $(printf '%d' "'$letter") ))
    done < <(echo -n "$1")

    while read -n1 letter; do
        (( word_2_val += $(printf '%d' "'$letter") ))
    done < <(echo -n "$2")

    if [ $word_1_val -gt $word_2_val ]; then
        echo $1
    else
        echo $2
    fi

    if [[ $word_1_val -gt $word_2_val ]]; then
        echo $1
    else
        echo $2
    fi

    return 0
}

sort_alphabetically "apple" "Apple"
sort_alphabetically "Banana" "apple"
sort_alphabetically "aPPle" "applE"

印刷品:

apple
apple
Banana
Banana
applE
applE

这使用进程替换并将输出重定向到 while 循环以一次读取一个字符,然后使用 printf 获取每个字符的十进制 ASCII 值。这就像从字符串创建一个临时文件,该文件将被自动销毁,然后一次读取一个字符。回显的 -n 表示 \n 字符,如果有来自用户输入或其他内容的字符,将被忽略。

来自 bash 手册页:

过程替换

进程替换允许使用文件名引用进程的输入或输出。它采用 <(list)>(list) 的形式。进程列表异步运行,其输入或输出显示为文件名。这个文件名作为扩展的结果作为参数传递给当前命令。如果使用 >(list) 形式,写入文件将为列表提供输入。如果使用 <(list) 形式,则应读取作为参数传递的文件以获得 list 的输出。支持命名管道 (FIFO) 或命名打开文件的 /dev/fd 方法的系统支持进程替换。

如果可用,进程替换与参数和变量扩展、命令替换和算术扩展同时执行。

来自stackoverflow post about printf

如果前导字符是单引号或双引号,则该值应是单引号或双引号之后字符的底层代码集中的数值。

注意:进程替换不符合 POSIX,但 Bash 以 bash 手册页中所述的方式支持它。


更新:上述方法不适用于所有情况!


上述解决方案在许多情况下都有效,但我们会遇到一些异常情况。

第一个词 第二个字 按字母顺序排在最后
苹果 苹果 苹果correct
苹果 苹果 苹果correct
apPLE 苹果 苹果incorrect
苹果 香蕉 香蕉correct
苹果 香蕉 苹果incorrect

以下解决方案得到所需的结果:

#!/bin/bash

sort_alphabetically() {
    [ $# -ne 2 ] && return 1

    local WORD_1="$1"
    local WORD_2="$2"
    local WORD_1_LOWERED="$(echo -n $1 | tr '[:upper:]' '[:lower:]')"
    local WORD_2_LOWERED="$(echo -n $2 | tr '[:upper:]' '[:lower:]')"

    if [ $(echo -e "$WORD_1\n$WORD_2" | sort | tail -n1) = "$WORD_1" ] ||\
       [ $(echo -e "$WORD_1_LOWERED\n$WORD_2_LOWERED" | sort | tail -n1) =\
         "$WORD_1_LOWERED" ]; then

        if [ "$WORD_1_LOWERED" = "$WORD_2_LOWERED" ]; then

            ASCII_VAL_WORD_1=0
            ASCII_VAL_WORD_2=0
            read -n1 FIRST_CHAR_1 < <(echo -n "$WORD_1")
            read -n1 FIRST_CHAR_2 < <(echo -n "$WORD_2")

            while read -n1 character; do
                (( ASCII_VAL_WORD_1 += $(printf '%d' "'$character") ))
            done < <(echo -n $WORD_1)
            
            while read -n1 character; do
                (( ASCII_VAL_WORD_2 += $(printf '%d' "'$character") ))
            done < <(echo -n $WORD_2)
            
            if [ $ASCII_VAL_WORD_1 -gt $ASCII_VAL_WORD_2 ] &&\
               [ "$FIRST_CHAR_1" \> "$FIRST_CHAR_2" ]; then

                echo "$WORD_1"
            elif [ $ASCII_VAL_WORD_2 -gt $ASCII_VAL_WORD_1 ] &&\
                 [ "$FIRST_CHAR_2" \> "$FIRST_CHAR_1" ]; then

                echo "$WORD_2"
            elif [ "$FIRST_CHAR_1" \> "$FIRST_CHAR_2" ]; then
                echo "$WORD_1"
            else
                echo "$WORD_2"
            fi
        else
            echo "$WORD_1"
        fi
    else
        echo $WORD_2
    fi

    return 0
}

sort_alphabetically "apple" "Apple"
sort_alphabetically "Apple" "apple"
sort_alphabetically "apPLE" "Apple"
sort_alphabetically "Apple" "apPLE"
sort_alphabetically "apple" "Banana"
sort_alphabetically "apple" "BANANA"

exit 0

印刷品:

apple
apple
apPLE
apPLE
Banana
BANANA
,

改变你的语法。 if [[ "Apple" -gt "apple" ]] 按预期工作。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。