bash字符串引用了多字args到数组

时间:2014-03-10 03:54:09

标签: arrays string bash arguments argument-passing

问题:

在bash脚本编写中,将包含多个单词的文字引号的字符串转换为具有相同解析参数结果的数组的最佳方法是什么?

争议:

存在许多问题,都是采用规避策略来避免问题而不是找到解决方案,这个问题提出了以下论点,并希望鼓励读者关注论点,如果你愿意接受,请参与挑战找到最佳解决方案。

提出的论点:

  1. 虽然有许多情况应该避免这种模式,但由于存在更适合的替代解决方案,作者认为仍然存在有效的用例。这个问题将尝试产生一个这样的用例,但不要仅仅认为它是可以想象的场景,它可能出现在现实世界的情况中。
  2. 您必须找到满足要求的最佳解决方案。该用例是专门针对其实际应用而选择的。您可能不同意所做的决定,但并不负责仅提供意见以提供解决方案。
  3. 在不修改输入或选择传输的情况下满足要求。两者都是通过现实场景特别选择来捍卫这些部分无法控制的叙述。
  4. 特定问题没有答案,这个问题旨在解决这个问题。如果你倾向于避免这种模式,那么只需避免这个问题,但如果你认为自己已经接受了挑战,那就看看你将如何处理这个问题。
  5. 有效用例:

    转换当前正在使用的现有脚本,以通过命名管道或类似流接收参数。为了最大限度地减少对开发人员控制之外的无数脚本的影响,决定不更改接口。现有脚本必须能够像以前一样通过新流实现传递相同的参数。

    现有实施:

    $ ./string2array arg1 arg2 arg3
    args=(
        [0]="arg1"
        [1]="arg2"
        [2]="arg3"
    )
    

    必要的更改:

    $ echo "arg1 arg2 arg3" | ./string2array
    args=(
        [0]="arg1"
        [1]="arg2"
        [2]="arg3"
    )
    

    问题:

    正如Bash and Double-Quotes passing to argv指出的那样,文字引号未按预期进行解析。

    此工作台脚本可用于测试各种解决方案,它处理传输并制定可测量的响应。建议您专注于使用字符串作为参数获取的解决方案脚本,并且应该将$ args变量填充为数组。

    string2array工作台脚本:

    #!/usr/bin/env bash
    #string2arry
    
    args=()
    
    function inspect() {
      local inspct=$(declare -p args)
      inspct=${inspct//\[/\\n\\t[}; inspct=${inspct//\'/}; inspct="${inspct:0:-1}\n)"
      echo -e ${inspct#*-a }
    }
    
    while read -r; do
      # source the solution to turn $REPLY in $args array
      source $1 "${REPLY}"
      inspect
    done
    

    标准解决方案 - FAILS

    将字符串转换为以空格分隔的单词数组的解决方案适用于上面的第一个示例:

    #solution1
    
    args=($@)
    

    不受欢迎的结果

    不幸的是,标准解决方案会为引用的多字参数产生不希望的结果:

    $ echo 'arg1 "multi arg 2" arg3' | ./string2array solution1
    args=(
        [0]="arg1"
        [1]="\"multi"
        [2]="arg"
        [3]="2\""
        [4]="arg3"
    )
    

    挑战:

    使用工作台脚本提供一个解决方案片段,它将为收到的参数生成以下结果。

    期望的结果:

    $ echo 'arg1 "multi arg 2" arg3' | ./string2array solution-xyz
    args=(
        [0]="arg1"
        [1]="multi arg 2"
        [2]="arg3"
    )
    

    解决方案应该与各种方式的标准参数解析兼容。对于提供的解决方案,应通过以下单元测试。如果您能想到单元测试中目前缺少的任何内容,请发表评论,我们可以对其进行更新。

    要求的单元测试

    更新:简化测试并包含Johnathan Leffer测试

    #!/usr/bin/env bash
    #test_string2array
    solution=$1
    function test() {
      cmd="echo \"${1}\" | ./string2array $solution"
      echo "$ ${cmd}"
      echo ${1} | ./string2array $solution > /tmp/t
      cat /tmp/t
      echo -n "Result : "
      [[ $(cat /tmp/t|wc -l) -eq 7 ]] && echo "PASSED!" || echo "FAILED!"
    }
    
    echo 1. Testing single args
    test 'arg1 arg2 arg3 arg4 arg5'
    echo
    echo 2. Testing multi args \" quoted
    test 'arg1 "multi arg 2" arg3 "a r g 4" arg5'
    echo
    echo 3 Testing multi args \' quoted
    test "arg1 'multi arg 2' arg3 'a r g 4' arg5"
    echo
    echo 4 Johnathan Leffer test
    test "He said, \"Don't do that!\" but \"they didn't listen.\""
    

7 个答案:

答案 0 :(得分:3)

declare内置似乎可以做你想要的事情;在我的测试中,你的inspect函数似乎无法正确测试所有输入:

# solution3
declare -a "args=($1)"

然后

$ echo "arg1 'arg2a arg2b' arg3" | while read -r; do
>  source solution3 "${REPLY}"
>  for arg in "${args[@]}"; do
>   echo "Arg $((++i)): $arg"
>  done
> done
Arg 1: arg1
Arg 2: arg2a arg2b
Arg 3: arg3

答案 1 :(得分:1)

所以我认为xargs实际上适用于所有测试用例,例如:

echo 'arg1 "multi arg 2" arg3' | xargs -0 ./string2array

答案 2 :(得分:1)

您可以使用declare代替eval,例如:

而不是:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
echo "Initial string: $string"
eval 'for word in '$string'; do echo $word; done'

执行:

declare -a "array=($string)"
for item in "${array[@]}"; do echo "[$item]"; done

但请注意,如果输入来自用户,则不会更安全!

所以,如果你尝试使用如下字符串:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'

你得到hostname评估(当然可能有类似rm -rf /的内容)!

非常非常简单的保护它的尝试只是替换像backtrick`和$:

这样的字符
string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'
declare -a "array=( $(echo $string | tr '`$<>' '????') )"
for item in "${array[@]}"; do echo "[$item]"; done

现在输出如下:

[aString that may haveSpaces IN IT]
[bar]
[foo]
[bamboo]
[bam boo]
[?hostname?]

有关使用不同方法的方法和专业知识的更多详细信息,您可以在这个好答案中找到:Why should eval be avoided in Bash, and what should I use instead?

另见https://superuser.com/questions/1066455/how-to-split-a-string-with-quotes-like-command-arguments-in-bash/1186997#1186997

但仍然留下了攻击的载体。 我非常希望使用双引号(“)中的字符串引用的bash方法,但不解释内容

答案 3 :(得分:0)

首次尝试

一旦检测到开放引号,就用组合的单词填充变量,并且只有在收到报价后才附加到数组。

解决方案

#solution2
j=''
for a in ${1}; do
  if [ -n "$j" ]; then
    [[ $a =~ ^(.*)[\"\']$ ]] && {
      args+=("$j ${BASH_REMATCH[1]}")
      j=''
    } || j+=" $a"
  elif [[ $a =~ ^[\"\'](.*)$ ]]; then
    j=${BASH_REMATCH[1]}
  else
    args+=($a)
  fi
done

单元测试结果:

$ ./test_string2array solution2
1. Testing single args
$ echo "arg1 arg2 arg3 arg4 arg5" | ./string2array solution2
args=(
    [0]="arg1"
    [1]="arg2"
    [2]="arg3"
    [3]="arg4"
    [4]="arg5"
)
Result : PASSED!

2. Testing multi args " quoted
$ echo 'arg1 "multi arg 2" arg3 "a r g 4" arg5' | ./string2array solution2
args=(
    [0]="arg1"
    [1]="multi arg 2"
    [2]="arg3"
    [3]="a r g 4"
    [4]="arg5"
)
Result : PASSED!

3 Testing multi args ' quoted
$ echo "arg1 'multi arg 2' arg3 'a r g 4' arg5" | ./string2array solution2
args=(
    [0]="arg1"
    [1]="multi arg 2"
    [2]="arg3"
    [3]="a r g 4"
    [4]="arg5"
)
Result : PASSED!

答案 4 :(得分:0)

第二次尝试

将元素附加到位而无需其他变量。

#solution3
for i in $1; do
  [[ $i =~ ^[\"\'] ]] && args+=(' ')
  lst=$(( ${#args[@]}-1 ))
  [[ "${args[*]}" =~ [[:space:]]$ ]] && args[$lst]+="${i/[\"\']/} " ||  args+=($i)
  [[ $i =~ [\"\']$ ]] && args[$lst]=${args[$lst]:1:-1}
done

答案 5 :(得分:0)

修改分隔符

在此解决方案中,我们将空格转换为逗号,删除引号并重置多字参数的空格,以允许正确的参数解析。

#solution4
s=${*//[[:space:]]/\l}
while [[ $s =~ [\"\']([^\"\']*)[\"\'] ]]; do
  s=${s/$BASH_REMATCH/${BASH_REMATCH[1]//\l/ }}
done
IFS=\l
args=(${s})

需要工作!!

答案 6 :(得分:0)

就地修改

让bash将字符串转换为数组然后循环以修复它。

args=($@) cnt=${#args[@]} idx=-1 chr=
for (( i=0; i<cnt; i++ )); do
  [[ $idx -lt 0 ]] && {
    [[ ${args[$i]:0:1} =~ [\'\"] ]] && \
       idx=$i chr=${args[$idx]:0:1} args[$idx]="${args[$idx]:1}"
    continue
  }
  args[$idx]+=" ${args[$i]}"
  unset args[$i]
  [[ ${args[$idx]: -1:1} == $chr ]] && args[$idx]=${args[$idx]:0:-1} idx=-1
done