Introduction to Shell

Shell gives you a quick and powerful way of having a programmable User Interface, where you can easily rustle up short programs to automate repetitive tasks. You can also use Shell to write CGI scripts.

Shell program = an interpreted program (i.e. not compiled). Also known as a "Shell script" or "batch file" of UNIX commands. Like .BAT files in DOS/Windows.

Shell program can have any extension (typically no extension). Edit a file, put some UNIX commands in it, perhaps strung together with some logic:

if condition
then
 rm f1
else
 rm f2
fi
Make it executable:
$ chmod +x file
Make sure it is in the PATH (which should include ".", the current directory) and just run it:
$ file
$ file &



Two different shells

You state at the top of the program which shell it is to run in:

  1. #!/bin/sh   - Normal Bourne Shell (sh), found on all UNIX systems. See this sample program shtest. Environment variables:

  2. #!/bin/csh   - C Shell (csh), common but not found everywhere. Has C-like syntax with Aliases (text substitutions analogous to #define macros) and Functions (like C functions, recursion allowed). See this sample program cshtest. Environment variables sometimes have slightly different syntax (see especially path):


Recommended - Learn to program in sh, as per tutorial material below. Learn program syntax for sh.

However, because DCU uses csh for command-line by default, be comfortable with environment variables in csh.
Don't need to learn program syntax for csh if just use csh for command-line, sh for programs.


Exercise - Get the test programs above to work, and see your environment variables:

cshtest
cshtest arg
cshtest lots of args

shtest




shtest looks like:

#!/bin/sh
# (run the following in standard Bourne shell)

clear

echo
echo Environment variables:
set
echo

if test "$1" = ""
then
 echo No command-line arguments
else
 echo Command-line arguments $*
 echo First argument $1
fi
Output with no args looks like:
Environment variables:
HOME=/users/ca2/humphrys
LD_LIBRARY_PATH=.:/usr/dt/lib:/usr/openwin/lib:/usr/lib
LD_RUN_PATH=/usr/dt/lib:/usr/openwin/lib
LIB=/usr/lib:/home/c++/lib
MAILCHECK=600
MANPATH=/usr/man:/usr/local/man:/usr/dt/share/man:/usr/openwin/man
OPENWINHOME=/usr/openwin
PATH=.:/users/ca2/humphrys/bin:/bin:/usr/local/bin:/usr/bin
PRINTER=staff
SHELL=/bin/csh
TERM=sun-cmd
USER=humphrys

No command-line arguments




cshtest looks like:

#!/bin/csh
# (run the following in C-shell)

clear

printf "\n"
echo Environment variables:
set
printf "\n"

if ( "$1" == "" ) then
 echo No command-line arguments
else
 echo Command-line arguments $*
 echo First argument $1
endif
Output with no args looks like:
Environment variables:
argv    ()
cdpath  (/users/ca2/humphrys /users/ca2/humphrys/public_html)
editmode        emacs
history 35
home    /users/ca2/humphrys
path    (. /users/ca2/humphrys/bin /bin /usr/local/bin /usr/bin)
printer staff
shell   /bin/csh
status  0
term    sun-cmd
user    humphrys

No command-line arguments




Question - In shtest, why not:

if test $1 = ""


Environment variables in parent and child processes

Note that change directory in Shell prog only has effect within that prog.
In general, environment variables changed within Shell script (such as "cwd" or "PWD" - current working directory) are only changed for the duration of that script.

They are not even changed by default for children of that script. You need to "export" them if you want to make them available to child processes:

export VAR

will make VAR available to a script called by this script, and any scripts called by that script, etc., without any further exporting needed. Only a single export statement is needed (in the top parent script).

You cannot, though, make changes in a script that take effect in the parent process. To write a "script" that changes the current process's environment variables you don't use a Shell program but rather write an alias, which is a command-line text substitution.

Note that environment variables are by default available to functions within the same Shell script without exporting.



What shells are installed?

Recall "which" shows you where (and if) a command is found in the path:

which sh
shows that "sh" is a binary program residing at:
/bin/sh
You know what to type next of course. It's:
ls -l /bin/*sh
To start using one as the command-line, just type its name. e.g.
ksh
"exit" to return.

Can have endless number of interpreters of source text,
e.g.   #!/usr/local/bin/perl   for Perl source
or   #!/usr/local/bin/rexx   for Rexx source


If you don't put a line at top of file saying what shell to run, here is what happens:
  1. If first char is # (comment), it runs in whatever shell you are in already.
  2. Any other first char (even space or blank line, also e.g. a command) and it runs in sh.

e.g. See C shell rules.



Useful things for Shell programs (or "Shell scripts")


Remember pipes and redirection

UNIX separates "ordinary output" (standard output, stdout, 1>, >)
from "error output" (standard error, stderr, 2>)

prog > output 2> errors


> /dev/null		to get rid of some unwanted output
			e.g. error/warning messages from compilation/search
			(redirects it into a non-existent file)



#                       comment

$*           all arguments to the Shell program
$1           1st argument, etc.
$0		name of prog
$#		no. of args

shift		shift args leftwards 
		this is useful if you want to remove some of the 
		 first args, then "shift" a couple of times,
		 and then do "for i in $*" with the remaining args
		e.g. grep (switches) (string) file1 ... filen

exit		exit the Shell script
exit 0		exit with a return code that other progs can query

$?		return code of last prog executed
		e.g. quiet grep: grep > /dev/null and check $?
		though grep may have -q (quiet) option anyway




strings and echo


echo                    print something on screen, followed by new line
echo -n                 print with no new line

printf                  print with no new line
printf "\n"             print with new line

echo "string"
echo 'string'

	It is useful to have 2 choices for quotes.
	If using one for something else, surround with the other.
	e.g. To search for single quote in file:

	grep ' file        syntax error (why?)
	grep "'" file      surround with quotes and it works
	grep '"' file      to search for the double quote itself

       Also see putting the current dir in the prompt


The 2 quotes are not exactly equal:

echo "--$HOME--"       --/users/ca2/humphrys--
echo '--$HOME--'       --$HOME--
echo '--'$HOME'--'     --/users/ca2/humphrys--


How to echo chars by numeric code




* (wildcards)


*                       all files
.*                      all hidden files

 "Hidden" in the sense that tools like ls won't list them by default.
 You can write your own tools of course to always list them by default.
 Not actually hidden in the security sense.

 Done for convenience not strict security, like write-protecting your own files
 (which is itself a kind of security since it stops some accidents).
 Doing things to  *  like  rm *  won't affect the  .*  files.
 Like the way  C:\Windows\  won't let you see the files first time.

 In fact  .*  files are probably only in your home directory,
 and your user files are strictly in sub-directories
 so the 2 never meet.
 Like how you strictly separate the files in  C:\Windows\
 from those in  C:\My Documents\

Q. Even on a 1-user system, why separate OS files from user files?


echo *                  echo all files
echo f*                 all files beginning with f
echo */*                files in next layer
*/*/*                   etc.

Important to realise it is the shell that interprets "*" and passes the result to echo or ls or your program. It is not actually echo or ls itself that parses it.
grep string *

# grep does not understand *

# but that's fine because grep does not actually RECEIVE *
# what happens is:
# the shell EXPANDS * to a list of files and passes these to grep
# so grep actually receives:

grep string f1 f2 .. fn
To see that it is the shell that expands it, assign it to an environment variable:
x="*"

echo $x

Q. Why have the shell interpret "*"? Why not just pass "*" as argument to progs?




Environment variables

Like global vars for all programs.
Note any environment vars that are declared within a program are local to that program only.


var=value               set environment variable
echo var                print the string "var"
echo $var               print value of environment variable
set                     show all environment variables

echo $home              get into the habit of using these
                        instead of the actual hard-coded values,
                        - makes scripts more portable
echo path is $path
echo $user
echo `hostname`
echo `arch`


Example:
date                         looks like: "Fri Jan 21 16:28:55 GMT 2000"
CURRENTDATE=`date`           remember backquotes
echo $CURRENTDATE            "Fri Jan 21 16:28:55 GMT 2000"

date "+%b %e"                looks like: "Jan 21" (see "man date")
CURRENTDATE=`date "+%b %e"`    
echo $CURRENTDATE            "Jan 21"

e.g. We want a random temporary filename:
filename="/tmp/random.`date +%H%M%S`.txt"
Perhaps not random enough. e.g. 2 clients accessing server at same second. A totally unique, random temporary name can be got from   $$   which is the process ID of this invocation of the shell script:
filename=/tmp/random.$$.txt
Or want a different seed each time for random-number generator.

Another example of backquotes: You share the same files across a number of UNIX systems on different hardware. You collect binaries for each platform, but keep them in separate directories underneath your   $home/bin directory. Then you set the path in your .cshrc file:
set path = ( $home/bin/`arch` ... )




Sample Shell program - rmifexists

for i in $*
do
        if test -f $i
        then
         rm $i
        fi
done

Sample usage - scape

cd $HOME/.netscape

rmifexists history*
rmifexists cookie*

rm -r cache
mkdir cache

cd $HOME

netscape &
Now to start my web browser, type "scape" instead of typing the web browser path directly (or clicking the icon). Now instead of having to point and click to clear out the history, cookies, and cache files, this is now automated.

Note that none of these "cd" commands have effect outside of the scope of this Shell script. Note that we can call "rmifexists" repeatedly in silence - if the file doesn't exist, no error message is generated.




Exercise

Why not:

        if test -f $*
        then
         rm $*
        fi




How to pass information to a program

In general, how to pass information to a program:

  1. Set environment variables. All programs can read these.

  2. Command-line arguments.
    Often see Program icon - Properties.

  3. Pipe to the program, where the program expects to read from standard input.

  4. Write to a file, where the program will use that file.
    See   .* files in home directory on UNIX.
    Equivalents exist on Windows. e.g. See   C:\Program Files\Netscape\prefs.js
    Often see Edit menu - Options or Preferences etc.

  5. Talk to the program while it is running. How to do this depends on the OS.




Shell Tutorials