word args to array

The Question:

In bash scripting, what is the best way to convert a string, containing literal quotes surrounding multiple words, into an array with the same result of parsed arguments?

The Controversy:

Many questions exist all applying evasive tactics to avoid the problem instead of finding a solution, this question raises the following arguments and would like to encourage the reader to focus on arguments and if you are up for it, partake in the challenge to find the optimum solution.

Arguments raised:

  • Although there are many scenarios where this pattern should be avoided, because there exists alternative solutions better suited, the author is of the opinion that valid use cases still remain. This question will attempt to produce one such use case, but make no claim to the viability thereof only that it is a conceivable scenario which may present itself in a real world situation.
  • You must find the optimum solution to satisfy the requirement. The use case was chosen specifically for its real world applications. You may not agree with the decisions that were made but are not tasked to give an opinion only to deliver the solution.
  • Satisfy the requirement without modifying the input or choice of transport. Both specifically chosen with a real world scenario to defend the narrative that those parts are out of your control.
  • No answers exist to the particular problem and this question aims to address that. If you are inclined to avoid this pattern then simply avoid the question but if you think you are up for the challenge lets see how you would approach the problem.
  • The Valid use case:

    Converting an existing script currently in use to receive parameters via named pipe or similar stream. In order to minimize the impact on the myriad of scripts outside of the developers control a decision was made to not change the interface. Existing scripts must be able to pass the same arguments via the new stream implementation as they did before.

    Existing implementation:

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

    Required change:

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

    The problem:

    As pointed out by Bash and Double-Quotes passing to argv literal quotes are not parsed as would be expected.

    This workbench script can be used to test various solutions, it handles the transport and formulates a measurable response. It is suggested that you focus on the solution script which gets sourced with the string as argument and you should populate the $args variable as an array.

    The string2array workbench script:

    #!/usr/bin/env bash
    #string2arry
    
    args=()
    
    function inspect() {
      local inspct=$(declare -p args)
      inspct=${inspct//[/nt[}; 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
    

    Standard solution - FAILS

    The solution for turning a string into a space delimited array of words worked for our first example above:

    #solution1
    
    args=($@)
    

    Undesired result

    Unfortunately the standard solution produces an undesired result for quoted multi word arguments:

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

    The Challenge:

    Using the workbench script provide a solution snippet that will produce the following result for the arguments received.

    Desired result:

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

    The solution should be compatible with standard argument parsing in every way. The following unit test should pass for for the provided solution. If you can think of anything currently missing from the unit test please leave a comment and we can update it.

    Unit test for the requirements

    Update: Test simplified and includes the Johnathan Leffer test

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

    The declare built-in seems to do what you want; in my test, it's your inspect function that doesn't seem work to properly test all inputs:

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

    Then

    $ 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
    

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

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

    Second attempt

    Append the element in place without the need for an additional variable.

    #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
    
    链接地址: http://www.djcxy.com/p/78036.html

    上一篇: 对加密数据进行全文搜索

    下一篇: 单词指向数组