我需要为我自己的脚本创建一个配置文件:
这是一个示例:
脚本:
#!/bin/bash
source /home/myuser/test/config
echo "Name=$nam" >&2
echo "Surname=$sur" >&2
内容/home/myuser/test/config
:
nam="Mark"
sur="Brown"
这样可行!
我的问题:这是正确的方法还是还有其他方法?
答案1
source
不安全,因为它会执行任意代码。这可能不是您关心的问题,但如果文件权限不正确,具有文件系统访问权限的攻击者可能会通过将代码注入到由其他安全脚本(例如初始化脚本。
到目前为止,我能够确定的最佳解决方案是笨拙的重新发明轮子解决方案:
myscript.conf
password=bar
echo rm -rf /
PROMPT_COMMAND='echo "Sending your last command $(history 1) to my email"'
hostname=localhost; echo rm -rf /
使用source
,这将运行echo rm -rf /
两次,并更改正在运行的用户的$PROMPT_COMMAND
.相反,请执行以下操作:
myscript.sh(重击 4)
#!/bin/bash
typeset -A config # init array
config=( # set default values in config array
[username]="root"
[password]=""
[hostname]="localhost"
)
while read line
do
if echo $line | grep -F = &>/dev/null
then
varname=$(echo "$line" | cut -d '=' -f 1)
config[$varname]=$(echo "$line" | cut -d '=' -f 2-)
fi
done < myscript.conf
echo ${config[username]} # should be loaded from defaults
echo ${config[password]} # should be loaded from config file
echo ${config[hostname]} # includes the "injected" code, but it's fine here
echo ${config[PROMPT_COMMAND]} # also respects variables that you may not have
# been looking for, but they're sandboxed inside the $config array
myscript.sh(兼容 Mac/Bash 3)
#!/bin/bash
config() {
val=$(grep -E "^$1=" myscript.conf 2>/dev/null || echo "$1=__DEFAULT__" | head -n 1 | cut -d '=' -f 2-)
if [[ $val == __DEFAULT__ ]]
then
case $1 in
username)
echo -n "root"
;;
password)
echo -n ""
;;
hostname)
echo -n "localhost"
;;
esac
else
echo -n $val
fi
}
echo $(config username) # should be loaded from defaults
echo $(config password) # should be loaded from config file
echo $(config hostname) # includes the "injected" code, but it's fine here
echo $(config PROMPT_COMMAND) # also respects variables that you may not have
# been looking for, but they're sandboxed inside the $config array
如果您在我的代码中发现安全漏洞,请回复。
答案2
解析配置文件,不执行它。
我目前正在编写一个使用极其简单的 XML 配置的应用程序:
<config>
<username>username-or-email</username>
<password>the-password</password>
</config>
在 shell 脚本(“应用程序”)中,这就是我获取用户名的方法(或多或少,我将其放入 shell 函数中):
username=$( xmlstarlet sel -t -v '/config/username' "$config_file" )
命令xmlstarlet
是XML小星,适用于大多数 Unices。在某些系统上,它安装为xml
.
我使用 XML 是因为应用程序的其他部分也处理 XML 文件中编码的数据,所以它是最简单的。
如果你更喜欢 JSON,这里有jq
这是一个易于使用的 shell JSON 解析器。
我的配置文件在 JSON 中看起来像这样:
{
"username": "username-or-email",
"password": "the-password"
}
然后我会在脚本中获取用户名:
username=$( jq -r .username "$config_file" )
还有TOML(“汤姆明显的最小语言”),具有多种语言的解析器。我个人最喜欢的解析器,是发行版的tomlq
一部分yq
https://kislyuk.github.io/yq/(jq
在幕后使用)。
配置文件的 TOML 版本如下所示
username = "username-or-email"
password = "the-password"
(字符串将是 JSON 编码的)...并且从中获取数据将与 JSON 情况一样简单(因为是tomlq
构建在 之上jq
):
username=$( tomlq -r .username "$config_file" )
答案3
这是一个干净且可移植的版本,与 Mac 和 Linux 上的 Bash 3 及更高版本兼容。
它在一个单独的文件中指定所有默认值,以避免在所有 shell 脚本中使用庞大、混乱、重复的“默认值”配置函数。它可以让你选择有或没有默认后备的阅读:
配置文件:
myvar=Hello World
配置.cfg.默认值:
myvar=Default Value
othervar=Another Variable
配置文件(这是一个图书馆,所以没有 shebang-line):
config_read_file() {
(grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}
config_get() {
val="$(config_read_file config.cfg "${1}")";
if [ "${val}" = "__UNDEFINED__" ]; then
val="$(config_read_file config.cfg.defaults "${1}")";
fi
printf -- "%s" "${val}";
}
test.sh(或任何您想要读取配置值的脚本):
#!/usr/bin/env bash
source config.shlib; # load the config library functions
echo "$(config_get myvar)"; # will be found in user-cfg
printf -- "%s\n" "$(config_get myvar)"; # safer way of echoing!
myvar="$(config_get myvar)"; # how to just read a value without echoing
echo "$(config_get othervar)"; # will fall back to defaults
echo "$(config_get bleh)"; # "__UNDEFINED__" since it isn't set anywhere
测试脚本说明:
- 注意全部test.sh 中 config_get 的用法用双引号括起来。通过将每个 config_get 用双引号括起来,我们确保变量值中的文本将绝不被误解为旗帜。它确保我们正确保留空格,例如配置值中连续的多个空格。
printf
那条线是什么?好吧,您应该注意这一点:echo
对于打印您无法控制的文本来说,这是一个错误的命令。即使您使用双引号,它也会解释标志。尝试设置myvar
(在config.cfg
) 设置为-e
,您将看到一个空行,因为echo
会认为它是一个标志。但printf
不存在这个问题。其中printf --
说“打印此内容,并且不要将任何内容解释为标志”,并"%s\n"
说“将输出格式化为带有尾随换行符的字符串,最后一个参数是 printf 要格式化的值。- 如果您不打算将值回显到屏幕,那么您只需正常分配它们,例如
myvar="$(config_get myvar)";
.如果您要将它们打印到屏幕上,我建议使用 printf 来完全安全地防止用户配置中可能存在的任何与回显不兼容的字符串。但是如果用户提供的变量 echo 就可以不是您正在回显的字符串的第一个字符,因为这是唯一可以解释“flags”的情况,所以类似的东西echo "foo: $(config_get myvar)";
是安全的,因为“foo”不以破折号开头,因此告诉 echo 其余部分string 也不是它的标志。 :-)
答案4
我在我的脚本中使用它:
sed_escape() {
sed -e 's/[]\/$*.^[]/\\&/g'
}
cfg_write() { # path, key, value
cfg_delete "$1" "$2"
echo "$2=$3" >> "$1"
}
cfg_read() { # path, key -> value
test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" | sed "s/^$(echo "$2" | sed_escape)=//" | tail -1
}
cfg_delete() { # path, key
test -f "$1" && sed -i "/^$(echo $2 | sed_escape).*$/d" "$1"
}
cfg_haskey() { # path, key
test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" > /dev/null
}
应该支持每个字符组合,除了按键不能有=
在其中,因为这是分隔符。其他任何事情都有效。
% cfg_write test.conf mykey myvalue
% cfg_read test.conf mykey
myvalue
% cfg_delete test.conf mykey
% cfg_haskey test.conf mykey || echo "It's not here anymore"
It's not here anymore
此外,这是完全安全的,因为它不使用source
or eval
。