我试图在字符串列表上运行for循环,其中一些字符串被引用而其他字符串不是这样的:
STRING='foo "bar_no_space" "baz with space"'
for item in $STRING; do
echo "$item"
done
预期结果:
foo
bar_no_space
baz with space
实际结果:
foo
"bar_no_space"
"baz
with
space"
我可以通过运行以下命令来实现预期的结果:
bash -c 'for item in '"$STRING"'; do echo "$item"; done;'
我希望这样做而不会产生新的bash进程或使用eval
,因为我不想承担执行随机命令的风险。
请注意我不控制STRING变量的定义,我通过环境变量接收它。所以我不能写出类似的东西:
array=(foo "bar_no_space" "baz with space")
for item in "${array[@]}"; do
echo "$item"
done
如果有帮助,我实际上要做的是将字符串拆分为可以传递给另一个命令的参数列表。
我有:
STRING='foo "bar_no_space" "baz with space"'
我想跑:
my-command --arg foo --arg "bar_no_space" --arg "baz with space"
答案 0 :(得分:1)
使用数组而不是普通变量。
arr=(foo "bar_no_space" "baz with space")
打印值:
print '%s\n' "${arr[@]}"
并致电你的命令:
my-command --arg "${arr[0]}" --arg "${arr[1]}" --arg "{$arr[2]}"
答案 1 :(得分:0)
聚会晚了几年,但是...
恶意输入:
SSH_ORIGINAL_COMMAND='echo "hello world" foo '"'"'bar'"'"'; sudo ls -lah /; say -v Ting-Ting "evil cackle"'
注意:我最初在那里有一个rm -rf
,但是后来我意识到,在测试脚本的变体时,这将是灾难的根源。
完美地转换为安全参数:
# DO NOT put IFS= on its own line
IFS=$'\r\n' GLOBIGNORE='*' args=($(echo "$SSH_ORIGINAL_COMMAND" \
| xargs bash -c 'for arg in "$@"; do echo "$arg"; done'))
echo "${args[@]}"
看到您确实可以像$@
一样传递这些参数:
for arg in "${args[@]}"
do
echo "$arg"
done
输出:
hello world
foo
bar;
sudo
rm
-rf
/;
say
-v
Ting-Ting
evil cackle
我很尴尬地说我花了多少时间研究这个问题才能弄清楚,但是一旦你痒了……你知道吗?
可以通过提供转义引号来欺骗xargs:
SSH_ORIGINAL_COMMAND='\"hello world\"'
这可以使输出中包含文字引号:
"hello
world"
否则可能会导致错误:
SSH_ORIGINAL_COMMAND='\"hello world"'
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
在任何一种情况下,它都无法启用代码的任意执行-参数仍会转义。
答案 2 :(得分: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 *
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
}
我可能会也可能不会将其更新为:
看起来好像很愚蠢……但是我很痒……哦。
答案 3 :(得分:-1)
你可以尝试这样的事情:
sh-4.4$ echo $string
foo "bar_no_space" "baz with space"
sh-4.4$ echo $string|awk 'BEGIN{FS="\""}{for(i=1;i<NF;i++)print $i}'|sed '/^ $/d'
foo
bar_no_space
baz with space