Search code examples
arrayslinuxbashshellvariable-expansion

How to write bash function to print and run command when the command has arguments with spaces or things to be expanded


In Bash scripts, I frequently find this pattern useful, where I first print the command I'm about to execute, then I execute the command:

echo 'Running this cmd: ls -1 "$HOME/temp/some folder with spaces'
ls -1 "$HOME/temp/some folder with spaces"

echo 'Running this cmd: df -h'
df -h

# etc.

Notice the single quotes in the echo command to prevent variable expansion there! The idea is that I want to print the cmd I'm running, exactly as I will type and run the command, then run it!

How do I wrap this up into a function?

Wrapping the command up into a standard bash array, and then printing and calling it, like this, sort-of works:

# Print and run the passed-in command
# USAGE:
#       cmd_array=(ls -a -l -F /)
#       print_and_run_cmd cmd_array
# See:
# 1. My answer on how to pass regular "indexed" and associative arrays by reference:
#    https://stackoverflow.com/a/71060036/4561887 and
# 1. My answer on how to pass associative arrays: https://stackoverflow.com/a/71060913/4561887
print_and_run_cmd() {
    local -n array_reference="$1"
    echo "Running cmd:  ${cmd_array[@]}"
    # run the command by calling all elements of the command array at once
    ${cmd_array[@]}
}

For simple commands like this it works fine:

Usage:

cmd_array=(ls -a -l -F /)
print_and_run_cmd cmd_array

Output:

Running cmd:  ls -a -l -F /
(all output of that cmd is here)

But for more-complicated commands it is broken!:

Usage:

cmd_array=(ls -1 "$HOME/temp/some folder with spaces")
print_and_run_cmd cmd_array

Desired output:

Running cmd: ls -1 "$HOME/temp/some folder with spaces"
(all output of that command should be here)

Actual Output:

Running cmd:  ls -1 /home/gabriel/temp/some folder with spaces
ls: cannot access '/home/gabriel/temp/some': No such file or directory
ls: cannot access 'folder': No such file or directory
ls: cannot access 'with': No such file or directory
ls: cannot access 'spaces': No such file or directory

The first problem, as you can see, is that $HOME got expanded in the Running cmd: line, when it shouldn't have, and the double quotes around that path argument were removed, and the 2nd problem is that the command doesn't actually run.

How do I fix these 2 problems?

References:

  1. my bash demo program where I have this print_and_run_cmd function: https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/argument_parsing__3_advanced__gen_prog_template.sh
  2. where I first documented how to pass bash arrays by reference, as I do in that function:
    1. Passing arrays as parameters in bash
    2. How to pass an associative array as argument to a function in Bash?

Follow-up question:

  1. Bash: how to print and run a cmd array which has the pipe operator, |, in it

Solution

  • If you've got Bash version 4.4 or later, this function may do what you want:

    function print_and_run_cmd
    {
        local PS4='Running cmd: '
        local -
        set -o xtrace
    
        "$@"
    }
    

    For example, running

    print_and_run_cmd echo 'Hello World!'
    

    outputs

    Running cmd: echo 'Hello World!'
    Hello World!
    
    • local PS4='Running cmd: ' sets a prefix for commands printed by the shell when the xtrace option is on. The default is + . Localizing it means that the previous value of PS4 is automatically restored when the function returns.

    • local - causes any changes to shell options to be reverted automatically when the function returns. In particular, it causes the set -o xtrace on the next line to be automatically undone when the function returns. Support for local - was added in Bash 4.4.

      From man bash, under the local [option] [name[=value] ... | - ] section (emphasis added):

      If name is -, the set of shell options is made local to the function in which local is invoked: shell options changed using the set builtin inside the function are restored to their original values when the function returns.

    • set -o xtrace (which is equivalent to set -x) causes the shell to print commands, preceded by the expanded value of PS4, before running them.

      See help set.