Friday, April 30, 2010

Passing bash arguments to an alias

A buddy of mine was trying to figure out why he could only pass $@ to the end of an alias and not somewhere before. His failed attempt looked something like this:
$ alias psgrep='ps wwax | grep $@ | grep -v grep'
$ psgrep Terminal
Usage: grep [OPTION]... PATTERN [FILE]...
Try `grep --help' for more information.
grep: Terminal: No such file or directory
His successful attempt was swapping the two greps as such:
$ alias psgrep='ps wwax | grep -v grep | grep $@'
$ psgrep Terminal
98893   ??  R      0:00.48 /Applications/Utilities/ -psn_0_3666815
At first glance, this does seem odd, how could it properly interpret $@ in the second by not the first? Well, to answer that question, we first need to ask if it is really doing just that. Is $@ properly interpreted in the second attempt? Lets find out:
$ alias psgrep='echo "ps wwax | grep -v grep | grep [$@]"'
$ psgrep Terminal
ps wwax | grep -v grep | grep [] Terminal
Hmmmm, that definitely does not appear to be the case. So, what about the first attempt then:
$ alias psgrep='echo "ps wwax | grep [$@] | grep -v grep"'
$ psgrep Terminal
ps wwax | grep [] | grep -v grep Terminal
Now that is very enlightening. The error from the first attempt is not from the first grep as it appeared to be, but the second grep. Since grep can take a list of files, it assumed the now appended 'Terminal' argument was another file to search through.

That solves our question of whether or not it is interpreted at all, but not why it doesn't work. Well, it does, but only when it was first assigned to the alias. So, when the set alias command is executed, $@ is interpreted (correctly) as undefined, and therefore the alias is set as such. When the alias is then executed later, $@ has already been interpreted and any arguments are appended to the aliased command. That is why the second attempt appeared to work correctly.

So how would do something like this? One approach would be to use a bash function:

(first a little cleanup from the previous examples)
$ unalias psgrep
(now the actual example)
$ function psgrep() { ps wwax | grep $@ | grep -v grep ; }
$ psgrep Terminal
98893   ??  R      0:01.83 /Applications/Utilities/ -psn_0_3666815
(finally, to unset the function if you wish)
$ unset psgrep

UPDATE: In yet another case of RTFM, I decided to read up on the man page for sh. It literally states "There is no mechanism for using arguments in the replacement text. If arguments are needed, a shell function should be used (see FUNCTIONS below)". So, there you go. At least my conclusion was in line with the documentation. :-P