如何在 shell 脚本中将变量设置为变量内部的变量

How to set a variable to the variable inside a variable in a shell script

我需要编写一个 POSIX shell 脚本来更改系统配置。在这样做之前,我想确保我编辑的任何文件都有备份。 此脚本的要求是使用 dmenu 提示用户是否已安装,如果未安装则使用 read。 我想要一个函数(下面命名为 communicate),它会根据在 运行、$dmenu.[=20 上设置的变量自动为我处理这个问题=]

我在向变量内部的变量写入时遇到问题,如下所示:

#!/usr/bin/env sh

[ $(command -v dmenu 2>/dev/null) ] && dmenu='true'

communicate(){
    description=""; options=""; outcome=""
    if [ $dmenu ]; then
        echo "$(printf "$options" | dmenu -i -p "$description")" >&0 | read $outcome
    else
        printf "$description $options "; read $outcome
    fi
}

backup(){
    [  ] && file="" || communicate 'Enter file: ' '' 'file'
    [ ! -f $file ] && backup ""
    cp "$file" "$file.bak"
}

select_interface(){
    [  ] && interface="" || communicate 'Select interface:' "$interfaces" 'interface'
}

backup 想要将用户输入保存到名为 $file 的变量中,而稍后 select_interface 想要保存到名为 [=28= 的变量中]$接口。 如果未安装 dmenu,则写入 $outcome 可以与 else 语句一起正常工作,而如果已安装,我似乎无法在传递 [=12= 的结果时触发 read 命令] 通过 STDIN 重定向进入读取,它在脚本之外工作。

有人能看出我做错了什么或者我如何才能做得更好吗? 我需要所有这些都在一个函数 communicate 中,作为与用户的通信代理。

声明

echo "$(printf "$options" | dmenu -i -p "$description")" >&0 | read $outcome

作为管道,导致 shell 将 echoread 作为 2 个单独的进程实现。 read仍然是一个分叉shell,它仍然设置变量$outcome,但它只在分叉shell中设置它,而不是在分叉(父)shell.

技术上正确的方法是:

eval $outcome=$\(printf "$options" \| dmenu -i -p "$description"\)'

但是 除了一次性代码,我建议不要使用 eval。

我也反对接受变量名设置的函数,这很难做到正确。

更简洁的方法:

#!/usr/bin/env sh

if [ $(command -v dmenu 2>/dev/null) ]; then
    communicate() {
        description=""
        options=""
        # also fixed this bug with the menu selection, each option needs to be in a new line
        printf "%s\n" $options | dmenu -i -p "${description}:"
    }
else
    communicate() {
        description=""
        options=""
        if [ -n "$options" ]; then
            optstring="options: ${options}; "
        else
            optstring=""
        fi
        read -p "${optstring}${description}: " outcome
        echo $outcome
    }
fi

backup() {
    if [ -n "" ]; then
        file=""
    else
        file=$(communicate 'Enter file')
    fi
    if [ -f "$file" ]; then
        cp "$file" "${file}.bak"
    else
        backup
    fi
}

select_interface() {
    if [ -n "" ]; then
        interface=""
    else
        interface=$(communicate "Enter interface" "$interfaces")
    fi
}