Skip to content

Instantly share code, notes, and snippets.

@Krush206
Last active November 18, 2023 15:51
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Krush206/bfcfd760be645150bb247f051e98c9d3 to your computer and use it in GitHub Desktop.
Save Krush206/bfcfd760be645150bb247f051e98c9d3 to your computer and use it in GitHub Desktop.

The C Shell isn't bad.

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.

Conclusion.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment