我遇到了在Bash脚本中将参数传递给命令的问题。
poc.sh:
#!/bin/bash
ARGS='"hi there" test'
./swap ${ARGS}
交换:
#!/bin/sh
echo "${2}" "${1}"
目前的输出是:
there" "hi
仅更改poc.sh(因为我认为swap正确地执行了我想要的操作),如何让poc.sh通过"嗨那里"并测试两个参数,用"嗨那里"周围没有引号?
答案 0 :(得分:17)
如果可能的话,不使用shell引用的字符串作为输入格式。
shlex
和xargs
之间的增量。)printf '%q'
,它将生成一个shell引用的字符串,其中包含任意变量的内容,但在POSIX sh标准中没有相应的字符串。eval
,这有很多安全问题。NUL分隔的流是一种更好的做法,因为它们可以准确地表示任何可能的shell数组或参数列表,而且没有任何歧义。
如果您使用shell引用从人工生成的输入源获取参数列表,则可以考虑使用xargs
来解析它。考虑:
array=( )
while IFS= read -r -d ''; do
array+=( "$REPLY" )
done < <(xargs printf '%s\0' <<<"$ARGS")
swap "${array[@]}"
...会将解析后的$ARGS
内容放入数组array
。如果您想要从文件中读取内容,请将<filename
替换为<<<"$ARGS"
。
如果您尝试编写符合POSIX sh的代码,这会变得更加棘手。 (我将在这里假设文件输入以降低复杂性):
# This does not work with entries containing literal newlines; you need bash for that.
run_with_args() {
while IFS= read -r entry; do
set -- "$@" "$entry"
done
"$@"
}
xargs printf '%s\n' <argfile | run_with_args ./swap
这些方法比运行xargs ./swap <argfile
更安全,因为如果有更多或更多的参数可以容纳,它会抛出错误,而不是将多余的参数作为单独的命令运行。
如果您需要比xargs
实现更准确的POSIX sh解析,请考虑使用Python shlex
模块:
shlex_split() {
python -c '
import shlex, sys
for item in shlex.split(sys.stdin.read()):
sys.stdout.write(item + "\0")
'
}
while IFS= read -r -d ''; do
array+=( "$REPLY" )
done < <(shlex_split <<<"$ARGS")
答案 1 :(得分:3)
嵌入式引号不保护空白;他们被字面上对待。在bash
中使用数组:
args=( "hi there" test)
./swap "${args[@]}"
在POSIX shell中,你使用eval
(这就是大多数shell支持数组的原因)。
args='"hi there" test'
eval "./swap $args"
像往常一样,非常确定您知道$args
的内容,并了解在使用eval
之前如何解析生成的字符串。
答案 2 :(得分:0)
这可能不是最强大的方法,但它很简单,似乎适用于您的情况:
## demonstration matching the question
$ ( ARGS='"hi there" test' ; ./swap ${ARGS} )
there" "hi
## simple solution, using 'xargs'
$ ( ARGS='"hi there" test' ; echo ${ARGS} |xargs ./swap )
test hi there
答案 3 :(得分:0)
这是一个用纯bash编写的带引号的字符串解析器(真是太有趣了!)
注意事项:就像上面的xargs示例一样,在引用转义符的情况下出现此错误。这可能是固定的,但是用实际的编程语言来做会更好。
MY_ARGS="foo 'bar baz' qux * "'$(dangerous)'" sudo ls -lah"
# Create array from multi-line string
IFS=$'\r\n' GLOBIGNORE='*' args=($(parseargs "$MY_ARGS"))
# Show each of the arguments array
for arg in "${args[@]}"; do
echo "$arg"
done
foo
bar baz
qux
*
这实际上是逐个字符地添加到当前字符串或当前数组中的。
set -u
set -e
# ParseArgs will parse a string that contains quoted strings the same as bash does
# (same as most other *nix shells do). This is secure in the sense that it doesn't do any
# executing or interpreting. However, it also doesn't do any escaping, so you shouldn't pass
# these strings to shells without escaping them.
parseargs() {
notquote="-"
str=$1
declare -a args=()
s=""
# Strip leading space, then trailing space, then end with space.
str="${str## }"
str="${str%% }"
str+=" "
last_quote="${notquote}"
is_space=""
n=$(( ${#str} - 1 ))
for ((i=0;i<=$n;i+=1)); do
c="${str:$i:1}"
# If we're ending a quote, break out and skip this character
if [ "$c" == "$last_quote" ]; then
last_quote=$notquote
continue
fi
# If we're in a quote, count this character
if [ "$last_quote" != "$notquote" ]; then
s+=$c
continue
fi
# If we encounter a quote, enter it and skip this character
if [ "$c" == "'" ] || [ "$c" == '"' ]; then
is_space=""
last_quote=$c
continue
fi
# If it's a space, store the string
re="[[:space:]]+" # must be used as a var, not a literal
if [[ $c =~ $re ]]; then
if [ "0" == "$i" ] || [ -n "$is_space" ]; then
echo continue $i $is_space
continue
fi
is_space="true"
args+=("$s")
s=""
continue
fi
is_space=""
s+="$c"
done
if [ "$last_quote" != "$notquote" ]; then
>&2 echo "error: quote not terminated"
return 1
fi
for arg in "${args[@]}"; do
echo "$arg"
done
return 0
}
我可能会也可能不会将其更新为:
看起来好像很愚蠢……但是我很痒……哦。