Search code examples
bashescapingechospecial-charactersquotes

How to unambiguously quote when printing all args passed to a bash function? But taking care to quote special characters, avoid using the backslash?


Basically I need a shell function called echo_Q (or an echo -Q option), than will echo a quoted argument list, while "[nicely]" strictly avoiding any backslash-quoting on any of the arguments, if possible.

A raw example of the native echo with some problem arguments:

echo another_script a "b b" "c 'c' c" 'd "d" d' "e "'"'"'e'"'"'" e" "ls -ltrd .??* | sort -k5,5 > f.lst" 'file `ls -t $HOME`' '|' '&' ';' "<"

Naturally, the native echo produces:

another_script a b b c 'c' c d "d" d e "'e'" e ls -ltrd .??* | sort -k5,5 > f.lst file `ls -t $HOME` | & ; <

In the above result: The quotes have have been evaluated, where necessary spaces and quotes have become opaque.

I've tried, printf " %q" "$@"; echo, and even:

echo_q(){
    sep="";
    for arg in "$@"; do
        printf "$sep%q" "$arg"
        sep=" "
    done
    echo
}

And get a fully "escaped" (verbose) output, where quoting is totally avoided.

echo_q another_script a "b b" etc..

another_script a b\ b c\ \'c\'\ c d\ \"d\"\ d e\ \"\'e\'\"\ e ls\ -ltrd\ .\?\?\*\ \|\ sort\ -k5\,5\ \>\ f.lst file\ \`ls\ -t\ \$HOME\` \| \& \; \>

In the above result: The output is technically what I want, but kind of "unpleasant" to read due to all the backslashes.

I would prefer (for readability) the output of:

another_script a 'b b' "c 'c' c" 'd "d" d' e\ \"\'e\'\"\ e 'ls -ltrd .??* | sort -k5,5 > f.lst' 'file `ls -t $HOME`' '|' '&' ';' '>'

Here is a "simple" hack I have been dabbling with:

special='$( )*?`<>\\|&;'
qq_special='$`'

echo_Q(){
    sep="";
    for arg in "$@"; do
        printf "$sep";
        sep=" "
        case "$arg" in
        (*["$special'"'"']*)
            case "$arg" in
            (*'"'*)
                case "$arg" in
                    (*"'"*)printf "%q" "$arg";;
                    (*)printf "'%s'" "$arg";;
                esac;;
            (*"'"*)
                case "$arg" in
                    (*"$qq_special"*) printf "%q" "$arg";;
                    (*) printf '"%s"' "$arg";;
                esac;;
            (*) printf "'%s'" "$arg";;
            esac;;
        (*) printf "%s" "$arg";;
        esac
        sep=" "
    done
    echo
}

But I'm hoping there is a standard (and simpler) way to "nicely" echo a function's arguments...

Bash does have: printf "%q", but what I need is a printf "%Q" to nicely quote an argument. Sadly, something so simple eludes me. :-(

Hints welcomed!


Solution

  • Bash does support a way to quote a parameter in a way that can be re-used as an input agin. Parameter expansion ${parameter@operator} supports a flag (introduced on bash v4.4)

    Q The expansion is a string that is the value of parameter quoted in a format that can be reused as input.

    set -- a "b b" "c 'c' c" 'd "d" d' "e "'"'"'e'"'"'" e" "ls -ltrd .??* | sort -k5,5 > f.lst" 'file `ls -t $HOME`' '|' '&' ';' "<"
    for arg in "$@"; do
        printf '%s\n' "${arg@Q}"
    done
    

    produces

    'a'
    'b b'
    'c '\''c'\'' c'
    'd "d" d'
    'e "'\''e'\''" e'
    'ls -ltrd .??* | sort -k5,5 > f.lst'
    'file `ls -t $HOME`'
    '|'
    '&'
    ';'
    '<'