Csh One-Liners

This is a collection of useful one-line csh-commands and short multi-line contructs, intended to give some practical hints to both novice and veteran csh and sh users. They are all intended to be used interactively, or in simple .cshrc, .login or .profile startup scripts.

The reader should refer to the relavent man pages in order to determine how the examples work. In particular, the reader is assumed to be familiar with:

cd shortcut

Some fool has created a directory named 'War_and_Peace_unabridged'. Here's a simple way to avoid having to type it out:

cd War*

As long as your wildcard pattern matches exactly ONE file or directory name, the cd command sees the right number of arguments (ie one).

The beauty of this is that it works in both the csh and sh, even if filename-completion is off.

Setting the path

Normally, the command search path is set at login, and left alone. But sometimes, you want to change it., and have to use something like:

set path = ( /new/dir $path )

and it's frustratingly easy to forget the $path on the end, and that's "bad".

Here's a simple csh alias makes it much harder to get into trouble:

alias path 'if ( "\!*" != "" ) set path=( \!* $path); echo $path'

The command path on it's own just prints the path, while path dir adds dir to the start of the path.

Bulk rename.

The 'mv' command lets you:

but will not allow you to rename files based on a pattern, such as renamng all the files *.txt to being *.text instead. Here's one way, based on variable modifiers

foreach fn ( *.txt )
mv $fn ${fn:r}.text

The above only works if there is a '.' in the filename.  Here's one that's a little more general:

foreach fn ( *_txt )
mv $fn `expr $fn : '\(.*\)_txt'`_text

Changing a lot of files from upper-case to lower-case is also tricky, since there is no built-in csh mechanism for converting between upper and lower case. In this case (no pun intended), it's gawk to the rescue

foreach fn ( *.txt )
mv $fn `echo $fn | gawk '{print tolower($0)}'`

Or maybe

ls *.txt | gawk '{system("mv " $0 " " tolower($0) )}'

{} expansion

Sometimes, you wan to rename or compare two files which have very similar names. In this case it can be useful to use {list,...} expansion feature of the csh.

mv source.c{,.old}

which expands to: mv source.c source.c.old


diff -c source.c{,.orig}

The key difference between {list,...} expansion and filename expansion ( * ) is that {list,...} expansion works for any text - not just filenames. You should consider it whenever you need a list of something other than files, such as usernames, hostnames ... just about anything.

The {list,...} expansions can be used more than once, or even nested, Consider the following example:

set path = ( /{s,,usr/{s,,openwin/,X11R6/}}bin )

which expands to:

set path = ( /sbin /bin /usr/sbin /usr/bin /usr/openwin/bin /usr/X11R6/bin )

Sometimes wildcards just aren't enough. Say you want a quick backup of some source-files you're working on, and you don't want all the *.o, binaries and other junk.

tar cvf /dev/fd0 {dir1,dir2}/{Makefile,*.{c,h,l,y,cc}}

which, using just filename expansion, you would have to type as:

tar cvf /dev/fd0 dir1/Makefile dir1/*.[chly] dir1/*.cc dir2/Makefile dir2/*.[chly] dir2/*.cc

{list,...} expansions are particulary powerful when used in conjunction with the foreach command.

foreach host ( 100.1.{10,20}.{1,2,3,4} )
echo ::: $host :::
finger -s user@$host

will check for user on hosts, and then

Another important point about {list,...} expansion is that the list is expanded exactly in the order specified. This contrasts with normal filename expansion, which is always sorted alphabetically.

set week = ( {Mon,Tues,Wednes,Thurs,Fri}day )

expands to: set week = ( Monday Tuesday Wednesday Thursday Friday )
in that order, and not sorted alphabetically.

Unfortunately, the csh does not allow the use of ';' in place of new-line characters at the start of a loop, such as:

foreach fn ( * ) ; file $fn ; end <- This doesn't work

In this case, it is necessary to resort to more indirect methods.

alias check_ultras '(echo "foreach host ( tlsc0{1,2,3,4} ) " ; echo "echo :::: "\$"host :::: " ; echo "rsh "\$"host top -n -d1 7" ; echo "end" ) | csh'

- or -

echo "while ( 1 ) \n" "xset "{,-}"led 2; sleep 1 \n" end | csh -f

Supressing the "Message of the Day" if it hasn't changed

I only want to see the 'message of the day' if it has been changed since the last time I logged in. The MotD is printed by the login program. login does not print the MotD (or check for mail) if your home-directory contains the file .hushlogin

echo ".hushlogin : /etc/motd ; :" | make -f - -q .hushlogin || touch .hushlogin && cat /etc/motd 

If you want to do something more complex than just cat /etc/motd , or just want something easier to read, use:

echo ".hushlogin : /etc/motd ; :" | make -f - -q .hushlogin
if ( $status == 1 ) then
     cat /etc/motd
     touch .hushlogin

Copying file-trees

Most people when they want to copy a whole directory tree, go straight for cp -r

This has the disadvantage that, if the tree contains symbolic links, these will be seperate files in the new tree. This costs unneccessary disk space, and can cause real headaches if you had special reasons for using symbolic links. The best way to copy directory trees is to use tar !!!

cd source_dir
tar cf - . | ( cd dest_dir; tar xvf - )

Or, if you are using GNU tar, you can use:

tar cf - -C source_dir . | tar xvf - -C dest_dir .

tar keeps symbolic links as symbolic links (unless you specifically use the -h option when creating the archive).

finding files

Someone's done a 'cp -r' and filled up the filesystem. Here's a simple way to locate the unwanted files...

find . -ctime -1 -print 

find has a partiularly powerful mechanism, -exec, which lets you execute commands on a per-file basis, while being selective about the files you're operating on.

Lets say we want to delete all the *.bak or *.backup files which are older than 30 days

find . \( -name '*.bak' -o -name *.backup \) -type f -atime +30 -exec rm '{}' \;

It would be even better if we only deleted the *.bak file if the original file was still available. In this case, we can use csh and test to help

find . \( -name '*.bak' -o -name *.backup \) -type f -atime +30 -exec csh -c 'if ( -f $1:r ) rm $1' '{}' \;

or if you're really paraniod, use 'test -s' to verify that the original file has not been truncated

find . -name '*.bak' -type f -atime +30 -exec csh -c 'test -s $1:r && rm $1' '{}' \;

One thing I find myself doing fairly reg

Protecting users from themselves - file permissions

find . -type l -exec gawk 'BEGIN { "ls -lag}' /dev/null \;

Now for something really tricky - renaming symbolic links which are broken. Suppose we want to find all the symbolic links beginning with old_prefix and change this to new_prefix-o -name *.backup \)

find . -type l -print -exec ls -lag '{}' \; | gawk '{q="'\''" ; file=$0 ; getline; split($0, ls, " -> "); link=ls[2]; gsub("^old_prefix", "new_prefix", link) ; if ( link != ls[2] ) system("rm " q file q"; ln -s "q link q" "q file q); }'

Since this is pretty long for a command, if you put it into a script, it makes send to break it up like this:

find . -type l -print -exec ls -lag '{}' \; | gawk '{q="'\''" ; file=$0 ; \
    getline; \
    split($0, ls, " -> "); \
    link=ls[2]; \
    gsub("^old_prefix", "new_prefix", link) ; \
    if ( link != ls[2] ) system("rm " q file q"; ln -s "q link q" "q file q); \

Obvious, really.

Seriously, note the following about the above solution:

head, tail, egrep, sed, gawk, expr, make, $var:q, grep -v,

expr - the one-line calculator

If you need an answer quickly, and don't care about the decimal places, the expr command can be used to do simple calculations. Just rememeber to quote the multiplication sign '*', stick to integers and put spaces between the arguments

For example, whats 27% of 83?

expr 27 \* 83

which gives 2349 - the answer multiplied by 100.

expr supports the 4 basic operators, + - / * and the modulo operator %

Supervising /bin/sh scripts

When writing scripts for critical operations, like bootup RC scripts (rc.local, rc.boot etc), you can end up with an unbootable machine if you get it wrong. Heres a 3-line solution that prevents a hanging script from holding things up indefinately.