如何正确地将 && 和 || 运算符用于两个以上的变量?

如何正确地将 && 和 || 运算符用于两个以上的变量?

我正在尝试比较两个以上变量的状态。用例是一个脚本,除其他选项外,应根据可用的可执行文件仅选择(或自动选择)几种“模式”中的一种。部分语法图是:

 [ [--fzf,-f]|[--rofi,-r]|[--dmenu,-d] ]

这些变量是根据命令参数的存在来定义的,因此,例如,如果在命令行参数上找到--fzf或,则关联的变量将设置为“1”,表示用户想要运行脚本,因为缺乏更好的方法条款,.-ffzffzf mode

下面的运算符语句&&解决了如果在命令行上找不到任何命令参数时会发生的情况。基本上,如果未选择“模式”,则脚本将自动选择一种且仅一种模式以分层方式使用。

下面的运算符语句||应该解决如果(由用户)选择模式但未找到底层可执行文件时会发生的情况。

这是 && 运算符的原始版本:

if [[ $fzf = 0 && $rofi = 0 ]]; then
        if command_exists fzf; then
            fzf=1
        elif command_exists rofi; then
            rofi=1
        fi
    fi

if [[ $fzf = 0 && $rofi = 0 && $dmenu = 0 ]]; then
        if command_exists fzf; then
            fzf=1
        elif command_exists rofi; then
            rofi=1
        elif command_exists dmenu; then
            dmenu=1
        fi
    fi

最后,这里是 || 的原文。操作员:

if [[ $rofi = 1 || $fzf = 0 ]]; then
        command_exists rofi || die "Could not find rofi in \$PATH"
        menu="$rofi_cmd"
elif [[ $fzf = 1 || $rofi = 0 ]]; then
        command_exists fzf || die "Could not find fzf in \$PATH"
        menu="$fzf_cmd"
else
        die "Could not find either fzf or rofi in \$PATH"
fi

if [[ $rofi = 1 || $fzf = 0 || $dmenu = 0 ]]; then
        command_exists rofi || die "Could not find rofi in \$PATH"
        menu="$rofi_cmd"
    elif [[ $fzf = 1 || $rofi = 0 || $dmenu = 0 ]]; then
        command_exists fzf || die "Could not find fzf in \$PATH"
        menu="$fzf_cmd"
    elif [[ $dmenu = 1 || $rofi = 0 || $fzf = 0 ]]; then
        command_exists dmenu || die "Could not find dmenu in \$PATH"
        menu="$dmenu_cmd"
    else
        die "Could not find either fzf or rofi or dmenu in \$PATH"
    fi

这似乎没有抛出任何错误,但我怀疑这既不是执行此操作的正确方法,并且可能无法按预期工作(因为它在使用-x和查看其输出时似乎报告了错误的值)。

我知道诸如此类的帖子一,但我(还)没有找到具有两个以上变量的示例(就像我上面尝试做的那样)。

以上原文部分摘自脚本。我基本上试图添加对的支持,dmenu因为它只支持rofifzf(如上所示)。

这里是完整修改的脚本。它需要password-store作为依赖项。

我正在使用 Bash 5.0.3。

如何正确地将 && 和 || 运算符用于两个以上的变量?

答案1

如果用户只需要选择其中之一,我只需使用一个变量来保存选择,并删除每个选项的变量。无论如何,我们并不真正关心所有组合,我们只想从三个组合中选择一个,另一个用于“未设置/默认”。

所以:

#!/bin/sh
cmd=             # empty value for default
case "$1" in
    --rofi|-r)  cmd=rofi ;;
    --fzf|-f)   cmd=fzf ;;
    --dmenu|-d) cmd=dmenu ;;
esac

if [ -z "$cmd" ]; then
    echo "no command set, looking for default"
    if command_exists fzf; then
        cmd=fzf
    elif
        ...
    fi
fi

echo "running with command $cmd"
# actually do something

此处,所选模式存储在 中cmd。实际上,我什至没有给用户发出两个命令的可能性,只读取第一个参数作为标志。现在,如果您使用getoptgetopts,那并不能真正实现,但我们可以在设置之前检查模式是否已经设置(再次)。

#!/bin/sh
error_if_cmd_set() {
    if [ -n "$1" ]; then
        echo "command already set"
        exit 1
    fi
}
cmd=
for arg in "$@"; do
    case "$1" in
        --rofi|-r)  error_if_cmd_set "$cmd"; cmd=rofi ;;
        --fzf|-f)   error_if_cmd_set "$cmd"; cmd=fzf ;;
        --dmenu|-d) error_if_cmd_set "$cmd"; cmd=dmenu ;;
    esac
done
# ...
echo "using command $cmd"

答案2

老实说,我不知道你想用这些支票做什么。如果您需要检查这些程序中至少有一个可用:

programs=(fzf rofi dmenu)
available=()

for prog in "${programs[@]}"; do
  location=$(type -P $prog) && available+=("$location")
done

(( ${#available[@]} == 0 )) && die "none of ${programs[*] are available"

# what is your logic for choosing one if multiple are available?
menu=${available[0]}


根据您的评论,将更多工作推入案例分支会更干净:

    fzf_exists=0
    rofi_exists=0
    dmenu_exists=0

    # reorder these if you want: the _last_ one to succeed is the default
    if command_exists dmenu; then
        dmenu_exists=1
        menu_default=$dmenu_cmd
    fi
    if command_exists rofi; then 
        rofi_exists=1
        menu_default=$rofi_cmd
    fi
    if command_exists fzf; then 
        fzf_exists=1
        menu_default=$fzf_cmd
    fi

    if [[ -z $menu_default ]]; then
        die "none of fzf, rofi, dmenu are available"
    fi

    while true; do 
        case "$1" in
            -f|--fzf) 
                (( fzf_exists )) || die "fzf is not available"
                [[ -n $menu ]]   && die "choose only one of fzf/rofi/dmenu"
                menu=$fzf_cmd
                shift 
                ;;
            -r|--rofi)
                (( rofi_exists )) || die "rofi is not available"
                [[ -n $menu ]]    && die "choose only one of fzf/rofi/dmenu"
                menu=$rofi_cmd
                shift 
                ;;
            -d|--dmenu)
                (( dmenu_exists )) || die "dmenu is not available"
                [[ -n $menu ]]     && die "choose only one of fzf/rofi/dmenu"
                menu=$dmenu_cmd
                shift 
                ;;
            # ...
        esac
    done

    : ${menu:=$menu_default}

请注意,该command_exists函数return 1不应该exit 1

答案3

和操作||&&工作正常。剧本的翻译方式存在问题,但尽管一再要求澄清逻辑,但没有得到任何答复。因此,我根据我如何理解复制的脚本正在工作,添加了这个问题。

一件有趣的事情是,问题中未发布的一些脚本被错误地翻译了:

if [[ $fzf = 1 && $rofi = 1 && $dmenu = 1 ]]; then
    die 'Either --fzf,-f or --rofi,-r or --dmenu,-d must be given, not more than one'
fi

快速回顾后可以看出,其意图是该声明应该更多地遵循以下内容:

if [[ $(( $fzf + $rofi + $dmenu )) > 1 ]]; then
    die 'Either --fzf,-f or --rofi,-r or --dmenu,-d must be given, not more than one'
fi

可能还有其他问题。提供所有相关信息非常重要。

相关内容