csh
and
tcsh
The csh
and tcsh
shells provide an
alias command that allows you to make up new commands as
abbreviations for longer, more complex commands. For example,
alias ll ls -ldefines a command named ll which lists files in long form (i.e. with protections, sizes, modification dates, etc.) You can use it by itself:
ll total 20 -rw-r--r-- 1 sbloch users 4583 Oct 13 16:10 hw2.html -rw-r--r-- 1 sbloch users 3273 Oct 13 16:46 hw3.html -rw-r--r-- 1 sbloch users 540 Oct 11 13:49 index.html drwx------ 2 sbloch users 4096 Oct 11 12:01 oldor with arguments:
ll ~/html/class/271/assignments/h* -rw-r--r-- 1 sbloch users 4583 Oct 13 16:10 /users/staff/math/sbloch/html/class/271/assignments/hw2.html -rw-r--r-- 1 sbloch users 3273 Oct 13 16:46 /users/staff/math/sbloch/html/class/271/assignments/hw3.htmlNote that the command ll is simply replaced by ls -l in the command line, leaving the rest intact.
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
:
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-0000and you wanted to look up entries in this file easily:
% 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-0000A natural way to write this is using grep:
alias friend grep ~/friendsdoesn'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 commandalias friend grep !:1 ~/friendsUnfortunately, 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:
alias friend grep \!:1 ~/friendsAnd 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:
% thrice bandersnatch bandersnatch bandersnatch bandersnatchOur first attempt might be
alias thrice echo !:1 !:1 !:1but again, the "!:1" is recognized before the alias command even starts, rather than when the thrice command is used; a working version is
alias thrice echo \!:1 \!:1 \!:1
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:
alias thrice echo \!:1 ; echo \!:1 ; echo \!:1Unfortunately, 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:
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.
alias showstuff date ; cal ; echo Files: ; lsdoesn't work, because again the semicolons apply before alias starts, rather than after showstuff is expanded.
alias showstuff 'date ; cal ; echo Files: ; ls'works much better.
Suppose we want a command lnd which lists, with details, all the non-directory files in the current directory. We could try
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:
alias lnd 'ls -l | grep -v ^d'which works.
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
alias savels ls -l >\!:1but the shell interprets the > character for output redirection before alias starts, rather than after savels is expanded. So instead we say
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:
alias savels 'ls -l | grep -v \!:1 >\!:1'
Let's go back to thrice, but suppose we wanted it to take arbitrarily many arguments and repeat them all three times:
% thrice this is a test this is a test this is a test this is a testWe recall that !* stands for all the arguments of the latest command, so we try
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:
alias thrice 'echo \!* \!* \!*'
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,
indir ~sbloch/html/class/271 ls -l *htmlshould cd to the
~sbloch/html/class/271directory, list all the HTML files in that directory, and cd back to where we were before. How do we write this?
alias indir 'set current=`pwd` ; cd \!:1 ; \!:2* ; cd $current'
Now, suppose we wrote the same thing using double quotes:
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:
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/sblochbecause "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.
In general, unless you have a good reason not to, always put the body of an alias in single-quotes.
alias commandname 'blah blah blah blah blah'
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
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:
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
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:
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.