Writing Aliases in Csh and Tcsh

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
2
3
4
Joe Schmoe     123 Main Street	212-987-6543
Jane Doe 975 First Ave 516-357-6420
Bob T. Slob 111 River Road 718-432-1098
Bill T. Pill 1 South Ave 516-877-0000

and you wanted to look up entries in this file easily:

1
2
3
4
5
% friend Joe
Joe Schmoe 123 Main Street 212-987-6543
% friend 516
Jane Doe 975 First Ave 516-357-6420
Bill T. Pill 1 South Ave 516-877-0000

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
2
% thrice bandersnatch
bandersnatch bandersnatch 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
2
% thrice this is a test
this is a test this is a test 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
2
3
4
5
6
7
8
9
panther% pwd
/users/staff/math/sbloch/public_html/class/archive/271/fall2005/notes
panther% indir ~sbloch/html/class/271 ls -l *html
-rw-r--r-- 1 sbloch users 5134 Oct 14 10:52 271_calendar.shtml
-rw-r--r-- 1 sbloch users 6753 Oct 15 19:43 index.html
-rw-r--r-- 1 sbloch users 6373 Apr 10 2002 old_index.html
-rw-r--r-- 1 sbloch users 8852 Sep 20 12:01 syllabus.html
panther% pwd
/users/staff/math/sbloch

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
2
alias longcmd 'echo this is a very very very very very very very very
very very long alias command'

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
2
alias longcmd 'echo this is a very very very very very very very very \
very very long alias command'

This time there’s no error message until you try longcmd, at which point it prints out

1
2
this is a very very very very very very very very
csh: very: Command not found.

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
2
alias longcmd 'echo this is a very very very very very very very very \\
very very long alias command'

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.