bash 脚本的 dd 样式参数

PSk*_*cik 19 bash

我想将参数传递给 dd 样式的 bash 脚本。基本上,我想要

./script a=1 b=43
Run Code Online (Sandbox Code Playgroud)

具有相同的效果

a=1 b=43 ./script
Run Code Online (Sandbox Code Playgroud)

我以为我可以通过以下方式实现这一目标:

for arg in "$@"; do
   eval "$arg";
done
Run Code Online (Sandbox Code Playgroud)

确保eval安全的好方法是什么,即"$arg"匹配静态(无代码执行)变量赋值?

或者有没有更好的方法来做到这一点?(我想保持这个简单)。

ric*_*ici 16

您可以在 bash 中执行此操作而无需 eval(并且无需人工转义):

for arg in "$@"; do
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; then
    declare +i +a +A "$arg"
  fi
done
Run Code Online (Sandbox Code Playgroud)

编辑:根据 Stéphane Chazelas 的评论,我在声明中添加了标志,以避免分配的变量已经被声明为数组或整数变量,这将避免在许多情况下declare评估key=val参数的值部分。(例如,+a如果要设置的变量已经声明为数组变量,则会导致错误。)所有这些漏洞都与使用此语法重新分配现有(数组或整数)变量有关,这通常是众所周知的外壳变量。

事实上,这只是一类注入攻击的一个实例,它同样会影响eval基于 -based 的解决方案:只允许已知的参数名称比盲目设置命令行中碰巧出现的任何变量要好得多。(例如,考虑如果命令行设置 会发生什么PATH。或者重置PS1以包括一些将在下一个提示显示时发生的评估。)

与其使用 bash 变量,我更喜欢使用命名参数的关联数组,这既更容易设置,也更安全。或者,它可以设置实际的 bash 变量,但前提是它们的名称在合法参数的关联数组中。

作为后一种方法的示例:

# Could use this array for default values, too.
declare -A options=([bs]= [if]= [of]=)
for arg in "$@"; do
  # Make sure that it is an assignment.
  # -v is not an option for many bash versions
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= &&
        ${options[${arg%%=*}]+ok} == ok ]]; then
    declare "$arg"
    # or, to put it into the options array
    # options[${arg%%=*}]=${arg#*=}
  fi
done
Run Code Online (Sandbox Code Playgroud)

  • `declare` 和 `eval` 一样危险(甚至可以说更糟,因为它没有那么明显的危险)。例如,尝试使用 `'DIRSTACK=($(echo rm -rf ~))'` 作为参数来调用它。 (3认同)

Sté*_*las 9

POSIX 一个(设置$<prefix>var而不是$var避免特殊变量的问题,如IFS/ PATH...):

prefix=my_prefix_
for var do
  case $var in
    (*=*)
       case ${var%%=*} in
         "" | *[!abcdefghijiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]*) ;;
         (*) eval "$prefix${var%%=*}="'${var#*=}'
       esac
  esac
done
Run Code Online (Sandbox Code Playgroud)

称为 as myscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2,它将分配:

my_prefix_x <= 1
my_prefix_PATH <= /tmp/evil
my_prefix_1 <= 2
Run Code Online (Sandbox Code Playgroud)


PSk*_*cik 6

lcd047 的解决方案使用硬编码DD_OPT_前缀重构:

while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
  eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
done
Run Code Online (Sandbox Code Playgroud)

frosschutz的大部分重构值得称赞。

我把它放在一个带有全局变量的源文件中:

DD_OPTS_PARSE=$(cat <<'EOF'
  while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
    eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
  done
EOF
)
Run Code Online (Sandbox Code Playgroud)

eval "$DD_OPTS_PARSE" 做所有的魔法。

函数的版本是:

DD_OPTS_PARSE_LOCAL="${PARSE_AND_REMOVE_DD_OPTS/DD_OPT_/local DD_OPT_}"

正在使用:

eval "$DD_OPTS_PARSE_LOCAL"

我用它制作了一个repo,包括测试和 README.md。然后我在我正在编写的 Github API CLI 包装器中使用它,并且我使用相同的包装器来设置所述repo的 github 克隆(引导很有趣)。

仅在一行中为 bash 脚本传递安全参数。享受。:)


Jon*_*ler 5

支持经典的 Bourne shell,并且 Bash 和 Korn shell 仍然支持,这是一个-k选项。当它生效时,dd命令行上任何地方的任何“ -like”命令选项都会自动转换为传递给命令的环境变量:

$ set -k
$ echo a=1 b=2 c=3
$ 
Run Code Online (Sandbox Code Playgroud)

说服它们是环境变量有点困难;运行这个对我有用:

$ set -k
$ env | grep '^[a-z]='   # No environment a, b, c
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: 
a=1
b=2
c=3
$ set +k
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: b=2 c=3
$
Run Code Online (Sandbox Code Playgroud)

第一个env | grep演示没有使用单个小写字母的环境变量。第一个bash显示没有传递给通过 执行的脚本的参数-c,并且环境确实包含三个单字母变量。该set +k取消-k,也显示了相同的命令现在已经传递给它的参数。(a=1被视为$0脚本;您也可以通过适当的回声来证明这一点。)

这实现了问题的要求——打字./script.sh a=1 b=2应该与打字相同a=1 b=2 ./script.sh

请注意,如果您在脚本中尝试这样的技巧,则会遇到问题:

if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi
Run Code Online (Sandbox Code Playgroud)

所述"$@"被处理逐字; 不会重新分析以找到赋值样式的变量(在bash和 中ksh)。我试过:

#!/bin/bash

echo "BEFORE"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='
if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

echo "AFTER"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='

unset already_invoked_with_minus_k
Run Code Online (Sandbox Code Playgroud)

并且仅already_invoked_with_minus_kexec'd 脚本中设置了环境变量。