Home > database >  How to write bash function to print and run command when the command has arguments with spaces or th
How to write bash function to print and run command when the command has arguments with spaces or th

Time:02-15

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?

CodePudding user response:

Check your scripts with shellcheck:

Line 2:
    local -n array_reference="$1"
             ^-- SC2034 (warning): array_reference appears unused. Verify use (or export if used externally).
 
Line 3:
    echo "Running cmd:  ${cmd_array[@]}"
                        ^-- SC2145 (error): Argument mixes string and array. Use * or separate argument.
                        ^-- SC2154 (warning): cmd_array is referenced but not assigned.
 
Line 5:
    ${cmd_array[@]}
    ^-- SC2068 (error): Double quote array expansions to avoid re-splitting elements.

You might want to research https://github.com/koalaman/shellcheck/wiki/SC2068 . We fix all errors and we get:

print_and_run_cmd() {
    local -n array_reference="$1"
    echo "Running cmd:  ${array_reference[*]}"
    # run the command by calling all elements of the command array at once
    "${array_reference[@]}"
}

For me it's odd to pass an array by reference in this case. I would pass the actual values. I often do:

prun() {
   # in the style of set -x
   # print to stderr, so output can be captured
   echo "  $*" >&2
   # or echo "  ${*@Q}" >&2
   # or echo " $(printf " %q" "$@")" >&2
   # or echo " $(/bin/printf " %q" "$@")" >&2
   "$@"
}
prun "${cmd_array[@]}"

How do I fix these 2 problems?

Incorporate into your workflow linters, formatters and static analysis tools, like shellcheck, and check the problems they point out.

And quote variable expansion. It's "${array[@]}".

CodePudding user response:

You can achieve what you want with DEBUG trap :

#!/bin/bash
 
trap 'trap_saved_command="${BASH_COMMAND#* }"' DEBUG
print_and_run_cmd(){
    echo "Running this cmd: $trap_saved_command"
    "$@"
}
print_and_run_cmd ls -1 "$HOME/temp/some folder with spaces"
# output ->
# Running this cmd: ls -1 "$HOME/temp/some folder with spaces"
# ...
  • Related