The C Shell is one of the most reliable Shells I ever worked with. The C Shell inherited features from and is the only continuation on the Thompson Shell and Mashey Shell (the Shells included with Research Unix and PWB Unix, respectively). Erroneously, the C Shell is regarded as bad and glitchy. I beg to differ, though. Due to its "limitations", the C Shell is one of the most reliable Shells to work with. I added some comments on this Perl developer's complaints on the C Shell, in the hope it'll serve as a guide on how to adequately write scripts, as well as to encourage people on not giving up.
The power of a system comes more from the relationships among programs than from the programs themselves.
By following this principle, it's possible to get many interesting things out of Shells and any program at all. Control structures (namely, if/else) can be replaced with &&
(AND operator) and ||
(OR operator), along with sub-shells.
Checking whether a given file exists or not; create if non-existent:
( ( : < myfile ) >& /dev/null && echo File exists. ) || ( echo mytext > myfile && echo File created. )
Checking whether a given file exists or not, and whether is a directory or not:
( : < Dir ) && ( : >> Dir ) && echo This is a file.
:
is like /dev/null
, but in program form:
: | dd of=Disk.img bs=1 seek=8g
: > myfile
Although the C Shell lacks functions, aliases serve as workaround:
alias function 'if -e \!$ then\
echo OK\
else\
echo Not OK\
endif'
A better workaround for functions is to recurse into the script:
#!/bin/csh -f
alias function 'set argv = ( \!* ) ; source "$0"'
( exit !( "$?main" ) ) && goto "$1" && shift
set main
set ret = "`function myfunc`"
echo "$ret"
exit
myfunc:
( function myfunc2 )
echo "A function."
exit
myfunc2:
echo "Another function."
exit
When wanting to always return on failure (equivalent to false
):
# Three options; pick one.
( `` ) >& /dev/null
( : < '' ) >& /dev/null
( exit 1 )
Or on success (equivalent to true
):
# Two options; pick one.
( : < ~ ) >& /dev/null
( exit 0 )
:
+ FIFO + sub-shell can be used for signaling, so that the script only resumes after a job is done:
mkfifo signal ; ( : < signal ; echo Signal sent. ) &
less /COPYRIGHT ; : > signal
Handling stderr
independently is done with a sub-shell:
mkfifo ~/filter
setenv filter "~/filter"
cat $filter & ( ( ls /root/ || echo No access. ) > $filter ) >& /dev/null
The history may not work properly in scripts, but having a pre-set of commands in a variable serves as workaround:
#!/bin/csh -f
set cmdlist = "`cat .cmdlist`"
echo -n 'Enter a number to execute a command from the history: '
set cmdexec = "$<"
( echo $cmdexec | grep -qa '[^0-9]' && \
echo 'Must be a number.' ) && exit 1
`echo $cmdlist[$cmdexec]` || \
cat -n .cmdlist
Read files per line (as in Bourne Shells, with read
):
#!/bin/tcsh -f
if $1 == "" eval 'echo Enter a file to read. ; \
exit 1'
set echo_style=none
set v="`cat $1`"
set i=1
while ( $i <= $#v )
echo "$v[$i]"
@ i++
end
Pipes and I/O redirection don't work well with multi-line aliases, except if eval
is issued. The workaround is to have the script in a variable and source it from a FIFO. The following is also a workaround for file descriptor handling, since it isn't a feature:
alias reads 'set v = "`cat \!$`"'
setenv qscr 'set echo_style=none\
set i=1\
while ( $i <= $#v )\
echo "$v[$i]"\
@ i++\
end'
mkfifo ~/qscr
alias readg '( ( echo "$qscr:q" > ~/qscr & ) ; source ~/qscr )'
Use shift
for even better resemblance of file descriptor handling:
setenv qscr 'while ( 1 <= $#v )\
( set echo_style=none;\\\
echo "$v[1]" )\
shift v\
end'
alias readg '( echo "$qscr:q" > ~/qscr & ) ; source ~/qscr'
$<
cannot read from redirections or pipes, except within sub-shells:
echo date | ( $< )
Tue Dec 7 16:35:58 UTC 2021
( $< ) < datecmd
Tue Dec 7 16:36:09 UTC 2021
To pipe or redirect in if
, as well as evaluate commands' exit status, a sub-shell is required:
if ( { ( grep -qa abc file ) && ( grep -qa 123 file2 ) } ) echo Success.
if ( { ( printf '123\nabc' | sed 's/123/okay/' ) } ) echo Success.
head
may replace $<
and may even work better in some cases:
set words = ( `head -1` )
Braces can be used to separate elements manually, rather than by words:
set words = {"an element","another element"}
Command substitution nesting is possible:
echo "`echo "\"'`echo "\"'\''`echo "\"'\'\\\'\''`echo "\"'\'\\\'\\\\\\\'\\\'\''`echo "\"'\'\\\'\\\\\\\'\\\\\\\\\\\\\\\'\\\\\\\'\\\'\''`echo "\"'\'\\\'\\\\\\\'\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\'\\\\\\\'\\\'\''`echo "\`"pwd"\`"`'\'\\\'\\\\\\\'\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\'\\\\\\\'\\\'\''\""`'\'\\\'\\\\\\\'\\\\\\\\\\\\\\\'\\\\\\\'\\\'\''\""`'\'\\\'\\\\\\\'\\\'\''\""`'\'\\\'\''\""`'\''\""`'\""`"
The following works too:
echo "`echo "\"\`"echo "\"\\\"\\\`\""echo "\"\\\"\\\\\\\"\\\\\\\`\\\"\""echo "\"\\\"\\\\\\\"\\\\\\\\\\\\\\\"\\\\\\\\\\\\\\\`\\\\\\\"\\\"\""echo "\"\\\"\\\\\\\"\\\\\\\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\"\\\\\\\"\\\"\""pwd"\"\\\"\\\\\\\"\\\\\\\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\"\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\"\\\\\\\"\\\\\\\`\\\\\\\"\\\"\\\`\\\"\"\`\""`"
Initializing a large empty array doesn't have to be hard:
set empty = {,}{,}{,}{,}{,}{,}{,}{,}{,}{,}
Something I learned very quickly is that "issues" can be worked-around with sub-shells or FIFOs. Sub-shells and FIFOs make great Shell tools, but are forgotten.
The parent Shell shouldn't be used to perform procedures, but to perform tests. What's preferred, instead, is to perform procedures in sub-shells.
Double quotes inside double quotes should be escaped with "\""
. The same applies to the dollar symbol, to prevent variable expansion "\$"
.
The :h
, :t
, :r
and :e
modifiers on filename variables are extremely useful shorthands that are much more verbose to implement in Bourne Shells.
Many bugs and limitations in C Shell actually come from the Thompson Shell and Mashey Shell, including parsing bugs.
It's a matter of professionalism. There are enough tools to work with a Shell, without the need to bloat it. The limited and unintuitive design of Shells prior to Bourne Shell make them more compliant to the Unix philosophy.
The C Shell may be ugly, but offers countless methods for one to make up their mind to work on a solution. Worse is better.