how to expand bash function parameters inside function / fix shellcheck SC2294

244 Views Asked by At

Assume a function that runs a command in another directory:

sub() {
  cd subdirectory
  eval "$@" || exit
}

I would like to be able to invoke this function with parameters that are expanded inside the subdirectory, e.g. sub ls '$(basename $(pwd))'

However, as the function is defined above, it does not pass shellcheck:

^-- SC2294 (warning): eval negates the benefit of arrays. Drop eval to preserve whitespace/symbols (or eval as string).

Obviously, if I remove the eval, then the parameter is not expanded and if I remove the quotes as well, then the parameter is expanded in the current directory. Which is not what I want.

I understand there are some suggestions in shellcheck's wiki, but I don't know how to make them work.

Is it possible to use eval correctly in this scenario -- without the downsides described in SC2294 -- or do I need to ignore this rule?

2

There are 2 best solutions below

0
On BEST ANSWER

Use eval "$*" instead of eval "$@". That will have exactly the same effect (assuming that IFS has its default value) and Shellcheck is happy with it. See Accessing bash command line args $@ vs $* for details of $@ versus $* (quoted and unquoted).

Use of eval is, justifiably, generally discouraged. See Why should eval be avoided in Bash, and what should I use instead?. Sometimes there's no better option though.

1
On

... Drop eval ...

So drop it.

sub() {
  cd subdirectory
  "$@" || exit

  # I would do:
  if ! "$@"; then
     exit
  fi
}

sub sh -c 'ls "$(basename "$(pwd)")"'

invoke this function with parameters that are expanded inside the subdirectory

Using eval is evil. Consider a different approach - define a function with the code that will be executed.

todo() {
   # properly escaped script
   ls "$(basename "$(pwd)")"
}
sub todo

And, overall, if this is for interactive use, and you know what you are doing and the problems with eval, just leave it and silence the warning. For example, watch arguments are run via sh, like watch 'echo $(date)'.

sub() {
    cd subdirectory
    sh -c "$*"
}