Search code examples
shellcommand-linezsh

How to escape a command line for logging?


I want to echo a command that a script is running for debugging purposes.

So, my script has a line that runs a command:

negate() {
    echo \( "$1" -negate \)
}
NEGATED_IMAGE="$(negate 'my image.png')"
convert $=NEGATED_IMAGE two.png -composite out.png

And I simply copy paste it above after an echo to see what command was run:

echo convert $=NEGATED_IMAGE two.png -composite out.png
convert $=NEGATED_IMAGE two.png -composite out.png

The log is not as useful as I want. The above logs

convert ( my image.png -negate ) two.png -composite out.png

Which cannot be copy-pasted into my own command line to run the same command, because the parentheses are not escaped, neither is the space in the my image.png argument escaped or quoted.

So, how can my script log a command line it will execute, in such a way that copy-pasting the logged line into my zsh prompt will run the same command?

So the above case should log something like

convert \( 'my image.png' -negate \) two.png -composite out.png

Because that is the actual command that the script is executing.


Solution

  • The standard output of $(image_one 'input image.png') becomes the first paramter of convert, hence convert sees as first parameter the string ( input image.png -negate ).

    When passing arguments to a program, it makes sense to first write a list of the parameters and which parameter position they should end up. From your description, I understand that convert is supposed to get the following parameters:

    • $1: (
    • $2: input image.png
    • $3: `-negate
    • $4: ')'

    The goal is therefore to create an array of 4 elements, which then can be interpolated into the call of convert. Also you want to use an auxiliary function which just receives the second argument and adds the other 3.

    Here is one possible approach:

    image_one() {
      arrname=${1?Array name missing}
      par2=$2 
      eval $arrname'=( "(" $par2 -negate ")" )
    }
    
    image_one myarr 'image image.png'
    convert "${myarr[@]}" two.png -composite out.png
    

    UPDATE

    The OP now asserted in a comment that his problem is not the construction of the parameter list, but that he wants to print out the convert command to stdout.

    To avoid writing the code twice, the solution is again to use an array, but now for all parts of the command. Example:

    cmd=( '(' 'my image.png' -negate ')' two.png -composite out.png )
    # Log the command
    echo -E -- ${(q)cmd}
    # Run the command
    $cmd
    

    Of course you can not catch redirections with this approach, since redirections are not part of the command itself. However, your example does not contain any redirection anyway.