Preserving quotes in bash function parameters

后端 未结 8 664
天涯浪人
天涯浪人 2020-12-05 05:13

What I\'d like to do is take, as an input to a function, a line that may include quotes (single or double) and echo that line exactly as it was provided to the function. For

相关标签:
8条回答
  • 2020-12-05 05:21

    Although @Peter Westlake's answer is correct, and there are no quotes to preserve one can try to deduce if the quotes where required and thus passed in originally. Personally I used this requote function when I needed a proof in my logs that a command ran with the correct quoting:

    function requote() {
        local res=""
        for x in "${@}" ; do
            # try to figure out if quoting was required for the $x:
            grep -q "[[:space:]]" <<< "$x" && res="${res} '${x}'" || res="${res} ${x}"
        done
        # remove first space and print:
        sed -e 's/^ //' <<< "${res}"
    }
    

    And here is how I use it:

    CMD=$(requote "${@}")
    # ...
    echo "${CMD}"
    
    0 讨论(0)
  • 2020-12-05 05:28

    I was in a similar position to you in that I needed a script to wrap around an existing command and pass arguments preserving quoting.

    I came up with something that doesn't preserve the command line exactly as typed but does pass the arguments correctly and show you what they were.

    Here's my script set up to shadow ls:

    CMD=ls
    PARAMS=""
    
    for PARAM in "$@"
    do
      PARAMS="${PARAMS} \"${PARAM}\""
    done
    
    echo Running: ${CMD} ${PARAMS}
    bash -c "${CMD} ${PARAMS}"
    echo Exit Code: $?
    

    And this is some sample output:

    $ ./shadow.sh missing-file "not a file"
    Running: ls "missing-file" "not a file"
    ls: missing-file: No such file or directory
    ls: not a file: No such file or directory
    Exit Code: 1
    

    So as you can see it adds quotes which weren't originally there but it does preserve arguments with spaces in which is what I needed.

    0 讨论(0)
  • 2020-12-05 05:29

    Bash will remove the quote when you pass a string with quote in as command line argument. The quote is simply not there anymore when the string is pass to your script. You have no way to know there is a single quote or double quote.

    What you probably can do is sth like this:

    doit VAR=42
    doit echo \'single quote $VAR\'
    doit echo \"double quote $VAR\"
    

    In your script you get

    echo 'single quote $VAR'
    echo "double quote $VAR"
    

    Or do this

    doit VAR=42
    doit echo 'single quote $VAR'
    doit echo '"double quote $VAR"'
    

    In your script you get

    echo single quote $VAR
    echo "double quote $VAR"
    
    0 讨论(0)
  • 2020-12-05 05:29

    If one's shell does not support pattern substitution, i.e. ${param/pattern/string} then the following sed expression can be used to safely quote any string such that it will eval into a single parameter again:

    sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"
    

    Combining this with printf it is possible to write a little function that will take any list of strings produced by filename expansion or "$@" and turn it into something that can be safely passed to eval to expand it into arguments for another command while safely preserving parameter separation.

    # Usage: quotedlist=$(shell_quote args...)
    #
    # e.g.:  quotedlist=$(shell_quote *.pdf)    # filenames with spaces
    #
    # or:    quotedlist=$(shell_quote "$@")
    #
    # After building up a quoted list, use it by evaling it inside
    # double quotes, like this:
    #
    #   eval "set -- $quotedlist"
    #   for str in "$@"; do
    #       # fiddle "${str}"
    #   done
    #
    # or like this:
    #
    #   eval "\$a_command $quotedlist \$another_parameter"
    #
    shell_quote()
    {
        local result=''
        local arg
        for arg in "$@" ; do
    
            # Append a space to our result, if necessary
            #
            result=${result}${result:+ }
    
            # Convert each embedded ' to \' , then insert ' at the
            # beginning of the line, and append ' at the end of
            # the line.
            #
            result=${result}$(printf "%s\n" "$arg" | \
                sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/")
        done
    
        # use printf(1) instead of echo to avoid weird "echo"
        # implementations.
        #
        printf "%s\n" "$result"
    }
    

    It may be easier (and maybe safer, i.e. avoid eval) in some situations to use an "impossible" character as the field separator and then use IFS to control expansion of the value again.

    0 讨论(0)
  • 2020-12-05 05:30

    The shell is going to interpret the quotes and the $ before it passes it to your function. There's not a lot your function can do to get the special characters back, because it has no way of knowing (in the double-quote example) whether 42 was hard-coded or if it came from a variable. You will have to escape the special characters if you want them to survive long enough to make it to your function.

    0 讨论(0)
  • 2020-12-05 05:34
    doit echo "'single quote $VAR'"
    doit echo '"double quote $VAR"'
    

    Both will work.

    bash will only strip the outside set of quotes when entering the function.

    0 讨论(0)
提交回复
热议问题