tags:

views:

44

answers:

2

How do you write a function in bash that executes the command that it is given as an argument, where

  • The given command may be an alias
  • Arguments must be passed on exactly as given; no evaluating may be done

In other words, how to write an as-transparent-as-possible wrapper function.

The goal of the wrapper function could for example be to set the current directory before and after the given command, and/or set environment variables, or time how long the given command takes,... As a simple example here I take a function that just prints a line and then executes the given command.

A first attempt:

function wrap1 {
   echo Starting: "$@"
   "$@"
}

You could use it like wrap1 echo hello. But the problem is you cannot do alias myalias echo and then call wrap1 myalias hello: it wouldn't resolve the alias.

Another attempt using eval:

function wrap2 {
   echo Starting: "$@"
   eval "$@"
}

Now calling an alias works. But the problem is it evaluates the arguments too. For example wrap2 echo "\\a" prints just a instead of \a because the arguments are evaluated twice.

shopt -s expand_aliases doesn't seem to help here either.

Is there a way to both evaluate aliases like wrap2, but still pass on the arguments directly like wrap1?

A: 

It seems to be possible with a double eval:

eval "eval x=($(alias y | cut -s -d '=' -f 2))"
# now the array x contains the split expansion of alias y
"${x[@]}" "${other_args[@]}"

So maybe your function could be written as follows:

wrap() {
    eval "eval prefix=($(alias $1 | cut -s -d '=' -f 2))"
    shift
    "${prefix[@]}" "$@"
}

However, eval is evil, and double eval is double evil, and aliases are not expanded in scripts for a reason.

Philipp
I agree that usually expanding aliases in a script is not a good idea, because you don't want an alias of the user to change what a command in a script does. But that's not relevant here because the command in which I'm trying to expand the alias is not a command of the script, it's a command typed by the user.
Wouter Coekaerts
I assume you meant "alias $1" instead of "alias y" in wrap().What this is doing is basically reimplementing alias-resolving. I'm hoping that can be avoided, but if there's really no other way, this approach might indeed work, thanks.To complete the solution, it would also need to deal with commands that are not aliases, aliases resolving to other aliases, recursive aliases,... I fear this would grow into quite some beast.
Wouter Coekaerts
Thanks, corrected `y` → `$1`.
Philipp
+1  A: 

You (uh, I) can use printf %q to escape the arguments. At first sight, escaping with printf and then doing eval always gives the same result as passing the arguments directly.

wrap() {
    echo Starting: "$@"
    eval $(printf "%q " "$@")
}
Wouter Coekaerts
What is `command=$(shift)`? AFAIK `shift` doesn't write to stdout.
Philipp
But `printf "%q"` looks very promising. Does this also work for indirect or recursive aliases?
Philipp
Awtch, the $(shift) was wrong of course. Fixed it. In fact, there's even no reason to make a difference between the first "word" (possibly the alias) and the rest of the command at all.
Wouter Coekaerts
It works for indirect and recursive aliases, because the resolving of the aliases is left up to bash.And it also seems to work fine for commands(!) or arguments that contain "weird" chars or spaces.
Wouter Coekaerts