Home > other >  Bash: how to print and run a cmd array which has the pipe operator, |, in it
Bash: how to print and run a cmd array which has the pipe operator, |, in it

Time:02-17

This is a follow-up to my question here: How to write bash function to print and run command when the command has arguments with spaces or things to be expanded

Suppose I have this function to print and run a command stored in an array:

# Print and run the cmd stored in the passed-in array
print_and_run() {
    echo "Running cmd:  $*"
    # run the command by calling all elements of the command array at once
    "$@"
}

This works fine:

cmd_array=(ls -a /)
print_and_run "${cmd_array[@]}"

But this does NOT work:

cmd_array=(ls -a / | grep "home")
print_and_run "${cmd_array[@]}"

Error: syntax error near unexpected token `|':

eRCaGuy_hello_world/bash$ ./print_and_run.sh 
./print_and_run.sh: line 55: syntax error near unexpected token `|'
./print_and_run.sh: line 55: `cmd_array=(ls -a / | grep "home")'

How can I get this concept to work with the pipe operator (|) in the command?

CodePudding user response:

If you want to treat an array element containing only | as an instruction to generate a pipeline, you can do that. I don't recommend it -- it means you have security risk if you don't verify that variables into your string can't consist only of a single pipe character -- but it's possible.

Below, we create a random single-use "$pipe" sigil to make that attack harder. If you're unwilling to do that, change [[ $arg = "$pipe" ]] to [[ $arg = "|" ]].

# generate something random to make an attacker's job harder
pipe=$(uuidgen)

# use that randomly-generated sigil in place of | in our array
cmd_array=(
  ls -a /
  "$pipe" grep "home"
)

exec_array_pipe() {
  local arg cmd_q
  local -a cmd=( )
  while (( $# )); do
    arg=$1; shift
    if [[ $arg = "$pipe" ]]; then
      # log an eval-safe copy of what we're about to run
      printf -v cmd_q '%q ' "${cmd[@]}"
      echo "Starting pipeline component: $cmd_q" >&2
      # Recurse into a new copy of ourselves as a child process
      "${cmd[@]}" | exec_array_pipe "$@"
      return
    fi
    cmd =( "$arg" )
  done
  printf -v cmd_q '%q ' "${cmd[@]}"
  echo "Starting pipeline component: $cmd_q" >&2
  "${cmd[@]}"
}

exec_array_pipe "${cmd_array[@]}"

See this running in an online sandbox at https://ideone.com/IWOTfO

CodePudding user response:

Do this instead. It works.

print_and_run() {
    echo "Running cmd: $1"
    eval "$1"
}

Example usage:

cmd='ls -a / | grep -C 9999 --color=always "home"'
print_and_run "$cmd"

Output:

Running cmd: ls -a / | grep -C 9999 --color=always "home"
(rest of output here, with the word "home" highlighted in red)
  • Related