bash脚本的dd样式参数


19

我想将参数传递给dd风格的bash脚本。基本上我要

./script a=1 b=43

具有与...相同的效果

a=1 b=43 ./script

我以为我可以做到:

for arg in "$@"; do
   eval "$arg";
done

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

还是有更好的方法来做到这一点?(我想保持简单)。


这是用bash标记的。您需要符合Posix的解决方案,还是接受bash解决方案?
rici

标签的意思是我的意思:)
PSkocik

好吧,您可以将其解析为带有=分隔符的模式,并使用更精心构造的评估进行分配。为了安全起见,为了私人使用,我照你做的那样做。
Orion 2015年

Answers:


16

您可以在bash中执行此操作,而无需评估(也无需人工转义):

for arg in "$@"; do
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; then
    declare +i +a +A "$arg"
  fi
done

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

实际上,这只是一类注入攻击的实例,它们将同样影响eval基于解决方案的解决方案:只允许使用已知的参数名称比盲目设置命令行中出现的任何变量要好得多。(例如,考虑如果命令行设置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

1
正则表达式似乎有括号错误。也许用这个来代替:^[[:alpha:]_][[:alnum:]_]*=
lcd047

1
@ lcd047:foo=是将foo设置为空字符串的唯一方法,因此应允许(IMHO)。我把支架固定好了,谢谢。
rici 2015年

3
declare几乎和危险一样危险eval(甚至可能还不如说危险那么明显)。例如,尝试使用'DIRSTACK=($(echo rm -rf ~))'as作为参数来调用它。
斯特凡Chazelas

1
@PSkocik:+x是“不是-x”。 -a=索引数组,-A=关联数组,-i=整数变量。因此:不是索引数组,不是关联数组,不是整数。
lcd047

1
请注意,在的下一版本中bash,您可能需要添加+c来禁用复合变量或+F禁用浮点变量。我仍然会用eval你知道你站在那里的地方。
斯特凡Chazelas

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

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

my_prefix_x <= 1
my_prefix_PATH <= /tmp/evil
my_prefix_1 <= 2

6

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

while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
  eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
done

弗罗斯特斯(大多数人为重构)值得赞扬。

我将其放在带有全局变量的源文件中:

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

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

函数的版本为:

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

正在使用:

eval "$DD_OPTS_PARSE_LOCAL"

我从中进行了回购,并提供了测试和README.md。然后,我在编写的Github API CLI包装器中使用了此代码,并使用相同的包装器设置了上述repo的github克隆(引导很有趣)。

bash脚本的安全参数传递仅需一行。请享用。:)


1
但您可以摆脱*=*掉并在没有=的地方停止替换key / val。(因为您正在重构):P
frostschutz

1
实际上,您可以摆脱for循环和if并使用$ 1代替,因为您要转移所有内容……
frostschutz

1
嘿,集思广益的证明。:)
lcd047

1
收集早晨的想法:您甚至可以摆脱keyand val,而只是写作eval "${1%%=*}"=\${1#*=}。但这远eval "$1"不止于此,因为@rici declare "$arg"显然不起作用。还要提防设置诸如PATHPS1
lcd047 2015年

1
谢谢-我认为那是求值变量。感谢您对我的耐心-这非常明显。无论如何,没有-除了想象的那样,它看起来不错。您知道,尽管可以将其扩展为可在任何shell中使用case。可能没关系,但以防万一您不知道...
mikeserv 2015年

5

一个-k选项是支持经典的Bourne shell,而Bash和Korn shell仍然支持。生效后,dd命令行上任何位置的所有“ -like”命令选项都会自动转换为传递给命令的环境变量:

$ set -k
$ echo a=1 b=2 c=3
$ 

说服它们是环境变量要困难一些。运行这个对我有用:

$ 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
$

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

这实现了问题所要解决的问题-键入./script.sh a=1 b=2应该与type相同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

所述"$@"被处理逐字; 不会重新分析以查找分配样式的变量(bashksh)。我试过了:

#!/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

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


很好的答案!有趣的是,尽管HOME是可更改的,但是它不会更改PATH,因此必须存在类似env vars的黑名单(至少包含PATH),以这种方式设置太危险了。我喜欢这是多么简短,并回答了这个问题,但我将继续使用sanitize + eval + prefix解决方案,因为它仍然更安全,因此更通用(在您不希望用户对环境造成麻烦的环境中) )。谢谢,+ 1。
PSkocik 2015年

2

我的尝试:

#! /usr/bin/env bash
name='^[a-zA-Z][a-zA-Z0-9_]*$'
count=0
for arg in "$@"; do
    case "$arg" in
        *=*)
            key=${arg%%=*}
            val=${arg#*=}

            [[ "$key" =~ $name ]] && { let count++; eval "$key"=\$val; } || break

            # show time
            if [[ "$key" =~ $name ]]; then
                eval "out=\${$key}"
                printf '|%s| <-- |%s|\n' "$key" "$out"
            fi
            ;;
        *)
            break
            ;;
    esac
done
shift $count

# show time again   
printf 'arg: |%s|\n' "$@"

它可以处理(几乎)RHS上的任意垃圾:

$ ./assign.sh Foo_Bar33='1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0' '1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33'
|Foo_Bar33| <-- |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0|
arg: |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33|

$ ./assign.sh a=1 b=2 c d=4
|a| <-- |1|
|b| <-- |2|
arg: |c|
arg: |d=4|

如果您不破坏第一个非x = y参数的循环,shift将会杀死错误的事物
frostschutz

@frostschutz好点,已编辑。
lcd047

概括它很不错。我认为可以简化一下。
PSkocik

您有机会查看我的编辑内容吗?
PSkocik

请看一下我的编辑。这就是我喜欢的方式(+可能只是shift代替shift 1)。否则,谢谢!
PSkocik

0

前一段时间,我决定alias从事这种工作。这是我的另一个答案:


但是,有时可以分开评估和执行这些语句。例如,alias可用于预评估命令。在下面的示例中,变量定义被保存到一个别名,该别名只有在$var其评估的变量不包含与ASCII字母数字或_不匹配的字节时才能成功声明。

LC_OLD=$LC_ALL LC_ALL=C
for var do    val=${var#*=} var=${var%%=*}
    alias  "${var##*[!_A-Z0-9a-z]*}=_$var=\$val" &&
    eval   "${var##[0-9]*}" && unalias "$var"
done;       LC_ALL=$LC_OLD

eval此处用于处理alias从带引号的varname上下文中调用新变量的操作-并非完全用于赋值。并且eval仅在前面的alias定义成功的情况下才被调用,尽管我知道很多不同的实现将接受别名的许多不同类型的值,但我还没有找到可以接受一个完全空的shell的外壳。 。

别名中的定义用于_$var,但这是为了确保不会写入任何重要的环境值。我不知道以_开头的任何值得注意的环境值,对于半私有声明而言,这通常是一个安全的选择。

无论如何,如果别名定义成功,它将声明一个名为$var的值的别名。并且eval只会alias在它也不以数字开头的情况下调用它-否则eval只会得到一个null参数。因此,如果同时满足这两个条件,请eval调用,alias并进行保存在中的变量定义alias,然后将新别名立即从哈希表中删除。


alias在这种情况下,也有用的是可以打印您的作品。在询问时alias将打印双引号的“可用于外壳执行的安全”语句。

sh -c "IFS=\'
    alias q=\"\$*\" q" -- \
    some args which alias \
    will print back at us

输出值

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.