Bash quoted array expansion

半腔热情 提交于 2019-12-02 20:32:16

Your problem is with echo. It is getting the correct number of parameters, with some parameters containing spaces, but it's output loses the distinction of spaces between parameters and spaces within parameters.

Instead, you can use printf(1) to output the parameters and always include quotes, making use of printf's feature that applies the format string successively to parameters when there are more parameters than format specifiers in the format string:

echo "Failed: foo:" $(printf "'%s' " "${mycmd[@]}")

That will put single quotes around each argument, even if it is not needed:

Failed: foo: 'command.ext' 'arg1 with space' 'arg2' 'thing' 'etc'

I've used single quotes to ensure that other shell metacharacters are not mishandled. This will work for all characters except single quote itself - i.e. if you have a parameter containing a single quote, the output from the above command will not cut and paste correctly. This is likely the closest you will get without getting messy.

Edit: Almost 5 years later and since I answered this question, bash 4.4 has been released. This has the "${var@Q}" expansion which quotes the variable such that it can be parsed back by bash.

This simplifies this answer to:

echo "Failed: foo: " "${mycmd[@]@Q}"

This will correctly handle single quotes in an argument, which my earlier version did not.

bash's printf command has a %q format that adds appropriate quotes to the strings as they're printed:

echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"

Mind you, its idea of the "best" way to quote something isn't always the same as mine, e.g. it tends to prefer escaping funny characters instead of wrapping the string in quotes. For example:

crlf=$'\r\n'
declare -a mycmd=( command.ext "arg1 with space 'n apostrophe" "arg2 with control${crlf} characters" )
echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"

Prints:

Failed: foo: command.ext arg1\ with\ space\ \'n\ apostrophe $'arg2 with control\r\n characters'

How about declare -p quotedarray?

-- edit --

Acctually, declare -p quotedarray will satisfy you purpose well. If you insist on the output format of the result, then I have a small trick would do the work, but just for the indexed array not associative one.

declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}

A cumbersome method (which only quotes arguments that contain spaces):

declare -a myargs=(1 2 "3 4")
for arg in "${myargs[@]}"; do
    # testing if the argument contains space(s)
    if [[ $arg =~ \  ]]; then
      # enclose in double quotes if it does 
      arg=\"$arg\"
    fi 
    echo -n "$arg "
done

Output:

1 2 "3 4"

By the way, with regards to quoting is lost on this pass, note that the quotes are never saved. " " is a special character that tells the shell to treat whatever is inside as a single field/argument (i.e. not split it). On the other hand, literal quotes (typed like this \") are preserved.

Another approach

# echo_array.sh

contains_space(){ [[ "$1" =~ " " ]]; return $?; }
maybe_quote_one(){ contains_space "$1"  &&  echo \'"$1"\'  ||  echo "$1"; }
maybe_quote(){ while test "$1"; do maybe_quote_one "$1"; shift; done; }

arridx(){ echo '${'$1'['$2']}'; }
arrindir(){ echo $(eval echo `arridx $1 $2`); }
arrsize(){ echo `eval echo '${'#$1'[@]}'`; }

echo_array()
{ 
    echo -n "$1=( "
    local i=0
    for (( i=0; i < `arrsize a`; i++ )); do
        echo -n $(maybe_quote "$(arrindir $1 $i)") ''
    done
    echo ")"
}

Usage:

source echo_array.sh
a=( foo bar baz "It was hard" curious '67 - 00' 44 'my index is 7th' )
echo_array a
# a=( foo bar baz 'It was hard' curious '67 - 00' 44 'my index is 7th'  )
arrindir a 7
# my index is 7th
arrindir a 0
# foo
arrsize a
# 8

$ bash --version

GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.

I like to put the code in a function so it is easier to reuse and document:

function myjoin
{
   local list=("${@}")
   echo $(printf "'%s', " "${list[@]}")
}

declare -a colorlist=()
colorlist+=('blue')
colorlist+=('red')
colorlist+=('orange')

echo "[$(myjoin ${colorlist[@]})]"

Note how I added the comma in the solution because I am generating code with a bash script.

[EDIT: fix problem that EM0 pointed out that it is returning ['blue',]]

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!