字符串到浮点数的比较在 shell 脚本中不起作用

String to float comparison not working in shell script

提问人:Shubham Jain 提问时间:7/26/2023 最后编辑:Shubham Jain 更新时间:7/27/2023 访问量:124

问:

字符串到浮动的比较在 shell 脚本中不起作用

REQUIRED_VERSION=2.38.0
VERSION=$(echo $FULL_DATA | cut -d ":" -f 5)   #This gives values as 2.10.0 OR 2.40

if [ $VERSION -gt $REQUIRED_VERSION ]]
  then
    echo "===  PASSED ==== "
  else
    echo "=== FAILED ==="
    #exit 1
fi

使用 zsh 出现如下错误

test.sh:60:浮点常数错误

它总是给出与 sh 传递的结果

我也尝试过 bc 但不起作用,可能缺少语法不确定。

请建议一些从字符串类型转换为浮点数的方法,我可以进一步与shell或zsh进行比较

字符串 bash shell 浮点 zsh

评论

0赞 Shawn 7/26/2023
bash仅支持整数数学运算。并且会给确实具有浮点数的工具带来问题。3.28.0
0赞 user1934428 7/26/2023
你有没有考虑过切换到一个实际上可以做浮点数的shell(例如zsh)?
0赞 Shubham Jain 7/26/2023
@Shawn - 感谢您的回复,是的,我也在某处读过它,您认为将其浮动到 num 并进行比较是个好主意吗?
0赞 Shubham Jain 7/26/2023
@user1934428 - 我没有尝试过这个.它支持浮动吗?我们有什么方法可以将字符串转换为浮点数吗?你能分享一个例子吗
1赞 Paul Pazderski 7/26/2023
版本字符串不是浮点数。1.2.3 与 3.5.1 相比应该如何?随着 beta 或 rc4 或“5.18.16-1~bpo11+1”等版本的添加,情况会变得更糟。stackoverflow.com/a/4024263/8165349 有一些答案来比较 Bash 中的版本。我会围绕.sort -V

答:

2赞 Ed Morton 7/26/2023 #1

您不能将版本作为任何类型的数字(包括浮点数)进行比较,也不能作为字符串进行比较,因为每个分隔的部分都需要单独进行数字比较。您需要专门将它们作为版本进行比较,例如使用 GNU sort for 将版本号列表按从低到高的顺序排序:.-V

$ cat tst.sh
#!/usr/bin/env bash

required_version=2.38.0
actual_version=10.7.45

highest_version=$(printf '%s\n%s\n' "$required_version" "$actual_version" | sort -V | tail -1)

if [[ $highest_version == $actual_version ]]; then
    echo "=== PASSED ==="
else
    echo "=== FAILED ==="
fi

$ ./tst.sh
=== PASSED ===

如果你没有GNU,你可以随时编写自己的比较函数,例如:sort

$ cat tst.sh
#!/usr/bin/env bash

required_version=2.38.0
actual_version=10.7.45

compare_vers() {
    local a b n

    IFS='.' read -r -a a <<< "$1"
    IFS='.' read -r -a b <<< "$2"

    (( "${#a[@]}" > "${#b[@]}" )) && n="${#a[@]}" || n="${#b[@]}"

    for (( i=0; i<n; i++ )); do
        if (( "${a[i]:0}" > "${b[i]:0}" )); then
            echo 'BIGGER'
            return
        elif (( "${a[i]:0}" < "${b[i]:0}" )); then
            echo 'SMALLER'
            return
        fi
    done

    echo 'EQUAL'
}

rslt=$(compare_vers "$actual_version" "$required_version")

if [[ $rslt == "BIGGER" ]]; then
    echo "=== PASSED ==="
else
    echo "=== FAILED ==="
fi

$ ./tst.sh
=== PASSED ===

评论

0赞 Shubham Jain 7/26/2023
那是工作 ED ..谢谢。。还有一个小疑问,如何在if语句中做大于和等于...对我来说,如果[[ $highest_version > $actual_version ]];有效但不>=
0赞 Shubham Jain 7/26/2023
我正在尝试这个->。如果 [[ $highest_version>$actual_version && $highest_version=$actual_version ]];->当它们也相等时它会失败,知道为什么吗?
0赞 Ed Morton 7/26/2023
With,或者您仍在尝试将版本作为字符串或数字进行比较。正如我所提到的,你不能这样做,因为版本不是一个数字(除非它只包含 1 ),所以版本不能用数字来比较,也不能像字符串比较那样按字母顺序逐个字符地比较它们。如果你告诉我你想做什么(而不是告诉我你是如何尝试的),我可以帮忙。>>=.
3赞 Eric Postpischil 7/26/2023 #2

版本号不是数学数字。Bash 没有内置的方法来比较版本号。该命令用于比较版本号和报告文件是否正确排序。你可以将其与 Bash 的功能一起使用,将即时输入传递给命令:sort-V-C<<<

#!/bin/bash -e

A="2.38.0"
B="2.3.1"

if sort -C -V <<<"$A"$'\n'"$B"; then
    echo "A is earlier than or the same as B."
else
    echo "A is later than B."
fi

该语法是用于生成换行符的 Bash 功能。$'\n'

Bash 允许您在命令之前使用 a 否定测试:!

if ! sort -C -V <<<"$A"$'\n'"$B"; then

请注意,将报告“2.38”早于“2.38.0”。sort

1赞 glenn jackman 7/26/2023 #3

这可以在纯 bash 中完成,但当然它需要更多的代码:

required_version=2.38.0

ver2float() {
    local parts part frac=""
    IFS=. read -ra parts <<<"$1"
    for part in "${parts[@]:1}"; do
        frac+=$(printf "%03d" "$part")
    done
    printf "%s.%s" "${parts[0]}" "${frac:-0}"
}

compare_floats() {
    (( 10#${1%.*}  > 10#${2%.*} )) && return 1
    (( 10#${1%.*} <  10#${2%.*} )) && return 0
    (( 10#${1#*.} <= 10#${2#*.} ))
}

for version in  1.0  2.37.99  2.38.0  2.38.1  12; do
    if compare_floats "$(ver2float "$required_version")" \
                      "$(ver2float "$version")" 
    then
        echo "$required_version is less than or equal to $version"
    else
        echo "$required_version is greater than $version"
    fi
done

这输出

2.38.0 is greater than 1.0
2.38.0 is greater than 2.37.99
2.38.0 is less than or equal to 2.38.0
2.38.0 is less than or equal to 2.38.1
2.38.0 is less than or equal to 12
1赞 Gairfowl 7/27/2023 #4

zsh附带一个比较版本号的功能:

autoload is-at-least
is-a-least 2.38 2.0
print $?
# => 1

它可以接受点或破折号作为分隔符,看起来它也可以处理字母:

#!/usr/bin/env zsh
autoload is-at-least
tstVer(){
  local m=nope
  is-at-least ${1:?} ${2:?} && m=WORKS
  printf 'need: %-6s have: %-6s %s\n' $1 $2 $m
}
tstVer 0.3 1.0
tstVer 3 4.3-33
tstVer 4.4 4.3-33
tstVer 2.3d 2.3c
tstVer 5.3.0 5.3
tstVer 5.3 5.3.0

# => need: 0.3    have: 1.0    WORKS
# => need: 3      have: 4.3-33 WORKS
# => need: 4.4    have: 4.3-33 nope
# => need: 2.3d   have: 2.3c   nope
# => need: 5.3.0  have: 5.3    WORKS
# => need: 5.3    have: 5.3.0  WORKS