The Shell and Shell Scripting
Most of us are pretty familar with the "UNIX shell". We use it, whether bash, sh, tcsh, zsh, or other varients, to start and stop processes, control the terminal, and to otherwise interact with the system.
Any, many of you have heard of, or made use of "shell scripting" -- the process of providing instructions to the shell in a simple, interpreted programming language.
In many ways the language of the shell is very powerful -- it has functions, conditionals, loops, for example. In other ways, it is weak -- it is completely untyped (everything is a string).
But, the real power of shell program doesn't come from the language but from the diverse library that it can call upon -- any program. Shell programming remains popular because it provides a quick and easy way to integrate command-line tools and filters to solve often complex problems.
The simplest scripts of all are nothing more than lists of commands. Consider the script below:
#!/bin/sh # A really simple script who am i # cs395 pts/3 Sep 8 12:11 (SUNW-KRB5-AUTH-DATA) date # Thu Sep 9 02:10:53 EDT 2004 pwd # /export/home/cs395/public_html/applications/ln
What to notice? Well, in general, anything after a # is a comment and is ignored by the shell. We see this used both as an entire line and next to each of several lines, where it shows example output.
The "#!/bin/sh" tells the shell to invoke /bin/sh to run the script. This is necessary because different users might be using different shells: sh, csh, bash, zcsh, tcsh, &c. And these shells have slightly different languages and build-in features. In order to ensure consistent operation, we want to make sure that the same shell is used to run the script each time. This is achieved by starting the specified shell and passing the script into its standard in.
Aside: The various shells are more the same than different. As a result, on many systems, there is actually one shell program cabable of behaving with different personalities. On these systems, the personality is often selected by soft linking different names to the same shell binary. Then, the shell looks at argv to observe how it was invoked, sets some flags to enable/disable behaviors, and goes from there.
The bulk of this simple script is a list of commands. These commands are executed, in turn, and the output is displayed. The commands are found by searching the standard search path PATH. PATH is a : delimited list of directories which should be searched for executibles. Here's my search path:
The command which, used as in which ls, will tell you which version of a command is bneing executed. This is useful if different versions might be in your search path. In general, the search path is traversed from left to right.
Aside: Notice that ".", the current working directory, is the last directory listed. This should almost certainly be the case. Placing it first is dangerous. And, I've got a personal story on this one. When I was a freshman, student UNIX accounts were created with this path incorrect -- and "." placed first.
This led to a collection of people putting bogus "ls" and "cd" commands into their home directories. These commands would appear to work -- but also send off an email to the system admin, "I've been a bad boy snooping around in other peoples directories. Please punish me severely."
Curious freshman would wander around the directory space, including the homes of others -- silently annoying the system administrators. No doubt, they wanted someone punished severely!
PATH discussed above is one example of a variable. It is what is known as an environment variable. It reflects one aspect of the shell environment -- where to look for executibles. Changing it changes the environment in which the shell executes programs. Environment variables are special in that they are defined before the shell begins.
Environment variables, like most other variables, can be redefined siply by assigning them a new value:
And, they are evaluated (their value is examined, using the $operator, as below:
echo $PATH PATH=$PATH:/usr/local/apache/bin:. echo $PATH
To create new variables, you simply assign them a value:
echo $GREGS_CAR GREGS_CAR="Tarus" echo $GREGS_CAR
All shell script variables are untyped (well, they really are strings) -- how they are interpreted often depends on what program is using them or what operator is manipulating or examing them.
Positionals, e.g. Command Line Arguments
Several special variables exist to help manage command-line arguments to a script:
- $# - represents the total number of arguments (much like argv)
- $0 - represents the name of the script, as invoked
- $1, $2, $3, .., $8, $9 - The first 9 command line arguments
- $* - all command line arguments
- $@ - all command line arguments
- "$@" - all command line arguments, where each argument is individually quoted.
Unlike other variables, positions can't be assigned values using the = operator. Instead, they can only be changed in a very limited way.
The set command sets these values. Consider the following example:
set a b c # $1 is now a # $2 is now b # $3 is now c
One thing that should be noted about the set command. It accepts arguments, itself. These begin with the - sign. As a result, it can get confused and begin to interprete values that it should be assigning to positionals. To avoid this, the -- flag can be used:
set -- -a- -b- -c- # $1 is now -a- # $2 is now -b- # $3 is now -c-
If there are more than 9 command-line arguments, there is a bit of a problem -- there are onyl 9 positionals: $1, $2, ..., $9. $0 is special and is the shell script's name.
To address this problem, the shift command can be used. It shifts all of the arguments to the left, throwing away $1. What would otherwise have been $10 becomes $9 -- and addressible. We'll talk more about shift after we've talked about while loops.
Quotes, Quotes, and More Quotes
Shell scripting has three different styles of quoting -- each with a diffent meaning:
- unquoted strings are normally interpreted
- "quoted strings are basically literals -- but $variables are evaluated"
- 'quoted strings are absolutely literally interpreted'
- `commands in quotes like this are executed, their output is then inserted as if it were assigned to a variable and then that variable was evaluated`
I think "quotes" and 'quotes' are pretty straight-forward -- and will be constantly reinforced. But, I do want to show an example using `quotes`:
day=`date | cut -d" " -f1` printf "Today is %s.\n" $day