Using command-line arguments
Sometimes you want an alias to deal explicitly with its arguments, rather than just leaving them at the end of the command. For example, suppose you had a file in your home directory named friends
:
1 | Joe Schmoe 123 Main Street 212-987-6543 |
and you wanted to look up entries in this file easily:
1 | % friend Joe |
A natural way to write this is using grep:
1 | alias friend grep ~/friends |
doesn’t work, because grep expects the string you’re searching for to be the first argument, followed by the file you’re searching. So we need to put the argument of friend just after the word grep, before the filename ~/friends
.
As it happens, csh
records the command you typed in its history before expanding aliases, so you can use the history substitution features to find out what the arguments were to the latest command (e.g. “friend Joe
“). To review…
!!
is the whole command line!*
is all the arguments of the command!:1
is the first argument of the command!:2
is the second argument of the command!$
is the last argument of the command
So let’s try
1 | alias friend grep !:1 ~/friends |
Unfortunately, this doesn’t work, because the shell recognizes “!:1” as a history reference before it executes the alias command; in other words, this takes the first argument of whatever command was executed just before the alias, and defines friend to always search for that word!
So we need a way to tell the shell not to try “!:1” as a history reference. As is so often the case in Unix, we do this with a backslash:
1 | alias friend grep \!:1 ~/friends |
And now the friend command works as desired.
For another (sillier) example, suppose we wanted a thrice command which took one argument and printed it three times:
1 | % thrice bandersnatch |
Our first attempt might be
1 | alias thrice echo !:1 !:1 !:1 |
but again, the “!:1” is recognized before the alias command even starts, rather than when the thrice command is used; a working version is
1 | alias thrice echo \!:1 \!:1 \!:1 |
Quoting
Protecting a sequence semicolon
Suppose we wanted the thrice command to print its argument three times, each on a separate line. An easy way to do this is with three separate echo commands, separated by semicolons:
1 | alias thrice echo \!:1 ; echo \!:1 ; echo \!:1 |
Unfortunately, the shell breaks the command line at semicolons before executing the alias command, so this has the effect of defining thrice to be simply “echo !:1”, and then printing the string “!:1” twice on separate lines immediately. We want the semicolons to apply after thrice is expanded, rather than before it is defined. So we use single-quotes:
1 | alias thrice 'echo \!:1 ; echo \!:1 ; echo \!:1' |
which works. (In this case, double-quotes would also have worked. For reasons we’ll see later, single-quotes are generally preferred for aliases.)
For another example, suppose we wanted a showstuff alias that prints out the current date, this month’s calendar, the word “Files:”, and a list of files in the current directory.
1 | alias showstuff date ; cal ; echo Files: ; ls |
doesn’t work, because again the semicolons apply before alias starts, rather than after showstuff is expanded.
1 | alias showstuff 'date ; cal ; echo Files: ; ls' |
works much better.
Protecting a pipe
Suppose we want a command lnd which lists, with details, all the non-directory files in the current directory. We could try
1 | alias lnd ls -l | grep -v ^d |
(recall that in an “ls -l” listing, directories always have “d” as the first character on the line). But the shell recognizes the pipe character before the alias command starts, so it defines lnd to stand for “ls -l”, and pipes the nonexistent output of the alias command into grep -v ^d
, which is not what we wanted. In fact, for reasons I don’t want to go into here, the alias doesn’t seem to have any effect: the lnd command isn’t recognized! Again, we’ll fix the problem with single-quotes:
1 | alias lnd 'ls -l | grep -v ^d' |
which works.
Protecting I/O redirection
The same issue arises if an alias tries to do I/O redirection. Suppose we wanted a savels command to do an ls -l but put the results into a specified file. We might try
1 | alias savels ls -l >\!:1 |
but the shell interprets the > character for output redirection before alias starts, rather than after savels is expanded. So instead we say
1 | alias savels 'ls -l >\!:1' |
If you try this savels command and look at the resulting file, you’ll notice that the file contains its own name. If we want only the other files in the directory, we can fix it:
1 | alias savels 'ls -l | grep -v \!:1 >\!:1' |
Protecting globbing wildcards
Let’s go back to thrice, but suppose we wanted it to take arbitrarily many arguments and repeat them all three times:
1 | % thrice this is a test |
We recall that !* stands for all the arguments of the latest command, so we try
1 | alias thrice echo \!* \!* \!* |
but we immediately get an error message, before we can even try thrice. The explanation is that the character * also means “all the filenames in the current directory”, and filename globbing (expansion of *, ?, [a-z], and similar patterns) happens before alias executes, rather than after thrice is expanded. Once again, single quotes to the rescue:
1 | alias thrice 'echo \!* \!* \!*' |
Single vs. double quotes: protecting variable expansion
All of the examples so far would have also worked using double quotes instead of single quotes. But let’s try indir, which treats its first parameter as a directory to move to, then treats the rest of the parameters as a command to execute in that directory. For example,
1 | indir ~sbloch/html/class/271 ls -l *html |
should cd to the
1 | ~sbloch/html/class/271 |
directory, list all the HTML files in that directory, and cd back to where we were before. How do we write this?
1 | alias indir 'set current=`pwd` ; cd \!:1 ; \!:2* ; cd $current' |
Now, suppose we wrote the same thing using double quotes:
1 | alias indir "set current=`pwd` ; cd \!:1 ; \!:2* ; cd $current" |
Since the expansion of shell and environment variables happens after the recognition of single quotes, but before the recognition of double quotes, the shell would expand the variable name “current” at the time indir is defined, rather than while it’s executing. If “current” doesn’t happen to be defined when you type the alias indir … command, you’ll get an error message; if it is defined, indir will work oddly:
1 | panther% pwd |
because “current” happened to be /users/staff/math/sbloch
at the time I defined the alias.
On the other hand, if you actually want to use the value of a variable at the time the alias is defined rather than at the time it’s used (uncommon but possible), then double quotes are the way to go.
Moral of the story
In general, unless you have a good reason not to, always put the body of an alias in single-quotes.
1 | alias commandname 'blah blah blah blah blah' |
Writing long aliases
If you’re writing an alias for a multi-stage pipe, or just involving a lot of words, it can easily get too long to fit on one line comfortably. If you try to type
1 | alias longcmd 'echo this is a very very very very very very very very |
you won’t get to the second line, because the shell will complain that the first line has mismatched quotation marks. So we use a backslash to tell the shell that the newline isn’t actually the end of the command:
1 | alias longcmd 'echo this is a very very very very very very very very \ |
This time there’s no error message until you try longcmd, at which point it prints out
1 | this is a very very very very very very very very |
The backslash prevented the shell from ending the alias command, but then longcmd expanded into something with a newline in the middle, and the shell at that time decided the newline meant the end of a command. So we fix it with another backslash:
1 | alias longcmd 'echo this is a very very very very very very very very \\ |
Now one of the backslashes prevents the shell from ending the alias command, and the other is still left to prevent the shell from ending the echo command when longcmd is expanded.
Whether you entirely understood that or not, the rule of thumb is that if an alias definition is more than one line long, put a double backslash at the end of each line except the last.