![通过用户输入设置变量,并在继续脚本之前检查所有变量是否已设置](https://linux22.com/image/180823/%E9%80%9A%E8%BF%87%E7%94%A8%E6%88%B7%E8%BE%93%E5%85%A5%E8%AE%BE%E7%BD%AE%E5%8F%98%E9%87%8F%EF%BC%8C%E5%B9%B6%E5%9C%A8%E7%BB%A7%E7%BB%AD%E8%84%9A%E6%9C%AC%E4%B9%8B%E5%89%8D%E6%A3%80%E6%9F%A5%E6%89%80%E6%9C%89%E5%8F%98%E9%87%8F%E6%98%AF%E5%90%A6%E5%B7%B2%E8%AE%BE%E7%BD%AE.png)
我正在构建一个自动 bash 脚本,它将在 Linux 服务器上安装和配置多个包。
我希望在多个服务器上重复使用这个脚本,但根据需要更改一些参数。这样我就不必在将来使用查找和替换进行多次更改来执行大量代码,我在整个脚本中使用了变量,因为它们可以简单地在脚本的顶部/开头定义它们的值。
由于某些变量是密码,我想避免将密码存储在纯文本文件中。我的解决方法是将变量值存储在加密文档中,我可以从中读取变量值,然后在脚本开头定义变量。这将通过运行一系列问题并询问我的输入来设置变量的值来完成。我已经弄清楚如何使用以下命令来执行此操作:
read -p "`echo -e 'Enter admin user password: \n\b'`" admin_user_password
echo "You entered '$admin_user_password"
bash 脚本编写中比较棘手的地方是,我想确保所有变量都已设置(不留空),并确保在脚本继续并自动执行其自己的操作之前正确输入它们。在开头设置变量是脚本中唯一需要用户交互的部分。为了让它发挥作用,我相当确定我会研究循环。
这是我希望在脚本的自动部分开始之前运行循环的顺序。
- 该脚本通过询问用户输入来请求变量
- 用户设置的变量将根据所有变量的列表进行检查。任何未在步骤 1 中输入/设置的变量都将通过其余问题警告用户“您尚未为 $some-variable 设置值”。
- 一旦设置了所有变量,它将打印出所有设置的变量及其值,并通过简单的是/否询问它们是否正确。如果它们不正确,我想再次返回步骤 1 重新解决问题。但是,如果变量和值正确,脚本将继续进入自动化部分。
这是我到目前为止所拥有的。
## TOP OF SCRIPT ##
script_variables=(
admin_user_name
admin_user_password
)
for i in "${script_variables[@]}"; do
read -p "`echo -e 'Enter value for: \n\b'`" ${script_variables[@]}
echo "You entered '${script_variables[@]}'"
if test -z "${script_variables[@]}"
then
echo "${script_variables[@]} has not been set"
# loopback to the top of the script
continue
else
while true; do
read -p "Are the variables ccorrect?" yn
case $yn in
[Yy]* ) echo "All variables have been set. The script will now continue."; sleep 2; break;;
[Nn]* ) continue;;
* ) echo "Please answer yes or no.";;
esac
fi
done
# Automated script commands below
我不确定上面的命令是否有效。即使它执行了continue
第 14 行的 where is,循环将返回到 ## TOP OF SCRIPT ## 部分,它也会再次询问我数组中的所有问题。在第二次问我问题时,它需要检查自身是否已设置变量。对于那些尚未设置的变量,这应该是我被重新询问的唯一问题。
我能做到的唯一方法是将变量测试部分放在开头。这仅意味着,在第一次被问及设置变量的问题时,我将收到一条消息,指出尚未设置任何变量。
我寻求帮助的原因是我很少处理循环。我的 Unix 知识主要是自学的,主要是通过在像这样的论坛上浏览互联网或在虚拟环境中尝试 Linux PC 上的命令,而且我还没有冒险深入循环。每当我使用循环时,它们都是我从互联网上获取并在我自己的脚本中使用的片段。显然,在上面的脚本中,我正在查看嵌套循环,这进一步增加了复杂性。
更新1
关于您出色的答案,我想调整显示的文本,以便在输入变量时将其输入到下面的新行中。
例如,我如何在
read -p ("Enter value for $varName \n\b " script_variables[$varName]
我想显示这样的东西:
Enter value for admin_user_name
>
我在互联网上看到了有关使用的指南,echo -e
以便\n\b
可以使用。我确实玩过,但变量并没有扩大。
https://stackoverflow.com/questions/8467424/echo-newline-in-bash-prints-literal-n
更新2
我正在考虑扩展您的脚本并实现一种从临时文件读取和存储变量的可选方法。我将能够通过设置适当的 Unix 权限来保护其他系统用户的安全,然后我将在脚本的最后删除该文件,这样就不会有它的痕迹。
在脚本的开头,我想检查是否存在包含变量的特定文本文件。如果文件确实存在,则检查是否已设置所有变量并与变量名称数组进行比较。如果文件中存储的变量不完整,则运行循环直到它们被设置。显然,只有当用户被问到“你确定这是正确的吗?”时回答“是”时,才会设置存储在文件中的变量。
注意,一旦我让这个脚本工作起来,我希望将它放在 GitHub 上,并用最终版本更新我的答案,以便其他人可以使用它,因为它必然会帮助其他人。
答案1
好的,首先是几个明显的问题。扩展到以空格分隔的${script_variables[@]}
整个数组。因此,当您在脚本开头定义数组时, $script_variables
您将始终看到错误,因此永远不会为空。当您想引用数组的元素时,需要使用特定的数字索引:第一个元素,第二个元素等。test -z "${script_variables[@]}"
"${script_variables[@]}"
"${script_variables[0]}"
"${script_variables[1]}"
其次,当你将read
一个值存入变量时,你需要将它存储在变量中。但是,您给出的read
扩展数组只是存储在数组中的值:
$ echo "${script_variables[@]}"
admin_user_name admin_user_password
更重要的是,您似乎希望将用户给出的值存储在一个变量中,然后您可以通过名称调用该变量。这是您设置var="foo"
然后拥有variableName="var"
并试图获取的值多变的命名var
(因此,在本例中为“foo”)称为“间接扩展”。其语法是${!variableName}
.例如:
$ var="foo"
$ variableName="var"
$ echo "${!variableName}"
foo
因此,您可以使用它,也可以使用两个数组:一个用于存储变量名称,另一个用于存储它们的值。或者,对于现代版本的 bash 来说更好的是,使用单个关联数组其键将是变量名称。这是使用间接扩展的脚本的工作版本:
#/bin/bash
script_variables=(
admin_user_name
admin_user_password
)
## This will be used to exit the loop
allSet="";
while [[ -z $allSet ]]; do
for varName in "${script_variables[@]}"; do
## No need to loop the whole thing, just loop
## until this particular variable has been set
while [[ -z ${!varName} ]]; do
read -p "Enter value for $varName: " $varName
done
done
## We will only exit the loop once all vars have been set.
## Now print and check them.
printf '\n=========\nYou have entered:\n'
for varName in "${script_variables[@]}"; do
printf '%s=%s\n' "$varName" "${!varName}"
done
while true; do
read -p "Are the variables correct? " yn
case $yn in
[Yy]* )
echo "All variables have been set. The script will now continue.";
## Setting this to 1 exits the top "while [[ -z $allSet ]]; do" loop
allSet=1
break;;
[Nn]* )
## Clear the stored values to start again
for varName in "${script_variables[@]}"; do
unset $varName
done
break;;
* )
echo "Please answer yes or no.";;
esac
done
done
这是使用关联数组的版本:
#/bin/bash
declare -A script_variables=(
[admin_user_name]=""
[admin_user_password]=""
)
## This will be used to exit the loop
allSet="";
while [[ -z $allSet ]]; do
## '${!array[@]}' returns all keys of an associative array
for varName in "${!script_variables[@]}"; do
read -p "Enter value for $varName: " script_variables[$varName]
done
## We will only exit the loop once all vars have been set.
## Now print and check them.
printf '\n=========\nYou have entered:\n'
for varName in "${!script_variables[@]}"; do
printf '%s=%s\n' "$varName" "${script_variables[$varName]}"
done
while true; do
read -p "Are the variables correct? " yn
case $yn in
[Yy]* )
echo "All variables have been set. The script will now continue.";
## Setting this to 1 exits the top "while [[ -z $allSet ]]; do" loop
allSet=1
break;;
[Nn]* )
## Clear the stored values to start again
for varName in "${!script_variables[@]}"; do
script_variables[$varName]=""
done
break;;
* )
echo "Please answer yes or no.";;
esac
done
done
现在,就我个人而言,我会使用稍微不同的方法。我不会设置所有内容,然后让用户最后检查,而是在输入时检查所有内容。这样,您就可以在一开始就得到错误,并且不需要重新设置所有变量,而只需重新设置出错的变量。像这样的东西:
#/bin/bash
declare -A script_variables=(
[admin_user_name]=""
[admin_user_password]=""
)
allSet=0;
while [[ $allSet -lt ${#script_variables[@]} ]]; do
for varName in "${!script_variables[@]}"; do
ok=""
while [[ $ok != [Yy]* ]]; do
read -p "Enter value for $varName: " script_variables[$varName]
read -p "You entered '${script_variables[$varName]}'. Is this correct? " ok
done
## If we're satisfied, increment the value of allSet
((allSet++))
done
done
## You can add a second test here, but just exit the script if it's wrong.
## There's no point in complicating your code if you're just going back to
## the beginning anyway: just exit and rerun it.
printf '\n=========\nYou have entered:\n'
for varName in "${!script_variables[@]}"; do
printf '%s=%s\n' "$varName" "${script_variables[$varName]}"
done
read -p "Is everything OK? (y/n; n will exit the script)": yn
if [[ $yn != [Yy]* ]]; then
exit
fi
echo "Everything correctly set, continuing!"