迭代引用字符串列表

时间:2017-11-23 22:58:53

标签: bash

我试图在字符串列表上运行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"

4 个答案:

答案 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)

已解决:xargs + subshel​​l

聚会晚了几年,但是...

恶意输入:

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

可以通过提供转义引号来欺骗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解析器

这是一个用纯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