Writing and Executing a Shell Script


Why should you write and use shell scripts? Shell scripts can save you time and typing, especially if you routinely use the same command lines multiple times every day. Although you could also use the history function (press the Up or Down keys while using bash or use the history command), a shell script can add flexibility with command-line argument substitution and built-in help.

Although a shell script won't execute faster than a program written in a computer language such as C, a shell program can be smaller in size than a compiled program. The shell program does not require any additional library support other than the shell or, if used, existing commands installed on your system. The process of creating and testing shell scripts is also generally simpler and faster than the development process for equivalent C language commands.

Note

Hundreds of commands included with Fedora are actually shell scripts, and many other good shell script examples are available over the Interneta quick search yields numerous links to online tutorials and scripting guides from fellow Linux users and developers. For example, the startx command, used to start an X Window session from the text console, is a shell script used every day by most users. To learn more about shell scripting with bash, see the Advanced Bash-Scripting Guide, listed in the "Reference" section at the end of this chapter. You will also find Sams Teach Yourself Shell Programming in 24 Hours a helpful guide to learning more about using the shell to build your own commands.


When you are learning to write and execute your first shell scripts, start with scripts for simple but useful tasks. Begin with short examples, and then expand the scripts as you build on your experience and knowledge. Make liberal use of comments (lines preceded with a pound # sign) to document each section of your script. Include an author statement and overview of the script as additional help, along with a creation date or version number. Write shell scripts using a text editor such as vi because it does not automatically wrap lines of text. Line wrapping can break script syntax and cause problems. If you use the nano editor, include its -w flag to disable line wrap.

In this section, you learn how to write a simple shell script to set up a number of aliases (command synonyms) whenever you log on. Instead of typing all the aliases every time you log on, you can put them in a file by using a text editor, such as vi, and then execute the file. Normally these changes are saved in systemwide shell configuration files under the /etc directory to make the changes active for all users or in your .bashrc, .cshrc (if you use tcsh), or .bash_profile files in your home directory.

Here is what is contained in myenv, a sample shell script created for this purpose (for bash):

#!/bin/sh alias ll='ls -l' alias ldir='ls -aF' alias copy='cp'


This simple script creates command aliases, or convenient shorthand forms of commands, for the ls and cp commands. The ll alias provides a long directory listing: The ldir alias is the ls command, but prints indicators (for directories or executable files) in listings. The copy alias is the same as the cp command. You can experiment and add your own options or create aliases of other commands with options you frequently use.

You can execute myenv in a variety of ways under Linux. As shown in this example, you can make myenv executable by using the chmod command and then execute it as you would any other native Linux command:

$ chmod +x myenv


This line turns on the executable permission of myenv, which can be checked with the ls command and its -l option like this:

$ ls -l myenv -rwxrwxr-x    1 winky   winky          11 Aug 26 17:38 myenv


Running the New Shell Program

You can run your new shell program in several ways. Each method produces the same results, which is a testament to the flexibility of using the shell with Linux. One way to run your shell program is to execute the file myenv from the command line as if it were a Linux command:

$ ./myenv


A second way to execute myenv under a particular shell, such as pdksh, is as follows:

$ pdksh myenv


This invokes a new pdksh shell and passes the filename myenv as a parameter to execute the file. A third way requires you to create a directory named bin in your home directory, and to then copy the new shell program into this directory. You can then run the program without the need to specify a specific location or to use a shell. You do this like so:

$ mkdir bin $ mv myenv bin $ myenv


This works because Fedora is set up by default to include the executable path $HOME/bin in your shell's environment. You can view this environment variable, named PATH, by piping the output of the env command through fgrep like so:

$ env | fgrep PATH /usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin: \ /usr/X11R6/bin:/sbin:/home/paul/bin


As you can see, the user (paul in this example) can use the new bin directory to hold executable files. Another way to bring up an environment variable is to use the echo command along with the variable name (in this case, $PATH):

$ echo $PATH /usr/kerberos/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/home/bball/bin


The Public Domain Korn Shell

The pdksh shell was originally created by Eric Gisin, and, like the original Korn shell by David Korn, is in the public domain. In Fedora, pdksh is named ksh. Two symbolic links, /usr/bin/pdksh and /usr/bin/ksh, point to the pdksh shell, named /bin/ksh. For more information about pdksh, see the /usr/share/doc/pdksh directory or read the ksh man page. You can download the latest copy of pdksh (in source-code form) by browsing to ftp://ftp.cs.mun.ca/pub/pdksh/.

On March 1, 2000, AT&T released the official version of the korn shell, named KornShell 93, as open source. If you are interested in trying the original korn shell by David Korn, download a copy from the KornShell web page at http://www.kornshell.com/.


Caution

Never put . in your $PATH in order to execute files or a command in the current directorythis presents a serious security risk, especially for the root operator, and even more so if . is first in your $PATH search order. Trojan scripts placed by crackers in directories such as /tmp can be used for malicious purposes, and are executed immediately if the current working directory is part of your $PATH.


Storing Shell Scripts for Systemwide Access

After you execute the command myenv, you should be able to use ldir from the command line to get a list of files under the current directory and ll to get a list of files with attributes displayed. However, the best way to use the new commands in myenv is to put them into your shell's login or profile file. For Fedora, and nearly all Linux users, the default shell is bash, so you can make these commands available for everyone on your system by putting them in the /etc/bashrc file. Systemwide aliases for tcsh are contained in files with the extension .csh under the /etc/profile.d directory. The pdksh shell can use these command aliases as well.

Note

To use a shell other than bash after logging in, use the chsh command from the command line or the system-config-users client during an X session. You'll be asked for your password (or the root password if using system-config-users), as well as the location and name of the new shell (refer to Table 15.1). The new shell becomes your default shell, but only if its name is in the list of acceptable system shells in /etc/shells. See Chapter 5, "First Steps with Fedora," for details on changing your shell.


Interpreting Shell Scripts Through Specific Shells

The majority of shell scripts use a shebang line (#!) at the beginning to control the type of shell used to run the script; this bang line calls for an sh-incantation of bash:

#!/bin/sh


A shebang line (it is short for sharp and bang, two names for # and !) tells the Linux kernel that a specific command (a shell, or in the case of other scripts, perhaps awk or Perl) is to be used to interpret the contents of the file. Using a shebang line is common practice for all shell scripting. For example, if you write a shell script using bash, but want the script to execute as if run by the Bourne shell, sh, the first line of your script will contain #!/bin/sh, which is a link to the bash shell. Running bash as sh causes bash to act as a Bourne shell. This is the reason for the symbolic link sh, which points to bash.

The Shebang Line

The shebang line is a magic number, as defined in /usr/share/magica text database of magic numbers for the Linux file command. Magic numbers are used by many different Linux commands to quickly identify a type of file, and the database format is documented in the section five man page named magic (read by using man 5 magic). For example, magic numbers can be used by the Linux file command to display the identity of a script (no matter what filename is used) as a shell script using a specific shell or other interpreter such as awk or Perl.


You might also find different or new environment variables available to your scripts by using different shells. For example, if you launch csh from the bash command line, you will find several new variables or variables with slightly different definitions, such as

$ env ... VENDOR=intel MACHTYPE=i386 HOSTTYPE=i386-linux HOST=thinkpad.home.org


On the other hand, bash might provide these variables or variables of the same name with a slightly different definition, such as

$ env ... HOSTTYPE=i386 HOSTNAME=thinkpad.home.org


Although the behavior of a shebang line is not defined by POSIX, variations of its use can be helpful when you are writing shell scripts. For example, as described in the wish man page, you can use a shell to help execute programs called within a shell script without needing to hard code pathnames of programs. The wish command is a windowing Tool Control Language (tcl) interpreter that can be used to write graphical clients. Avoiding the use of specific pathnames to programs increases shell script portability because not every UNIX or Linux system has programs in the same location.

For example, if you want to use the wish command, your first inclination might be to write

#!/usr/local/bin/wish


Although this works on many other operating systems, the script fails under Linux because wish is located under the /usr/bin directory. However, if you write the command line this way,

#!/bin/sh exec wish "$@"


the script will always find the correct binary.

Using Variables in Shell Scripts

When writing shell scripts for Linux, you work with three types of variables:

  • Environment variables Part of the system environment, you can use them in your shell program. New variables can be defined, and some of them, such as PATH, can also be modified within a shell program.

  • Built-in variables These variables, such as options used on the command (interpreted by the shell as a positional argument), are provided by Linux. Unlike environment variables, you cannot modify them.

  • User variables Defined by you when you write a shell script. You can use and modify them at will within the shell program.

A major difference between shell programming and other programming languages is that in shell programming, variables are not typedthat is, you do not have to specify whether a variable is a number or a string, and so on.

Assigning a Value to a Variable

Assume that you want to use a variable called lcount to count the number of iterations in a loop within a shell program. You can declare and initialize this variable as follows:

Command

Environment

lcount=0

pdksh and bash

set lcount=0

tcsh


Note

Under pdksh and bash, you must ensure that the equal sign (=) does not have spaces before and after it.


To store a string in a variable, you can use the following:

Command

Environment

myname=Sanjiv

pdksh and bash

set myname=Sanjiv

tcsh


Use the preceding variable form if the string doesn't have embedded spaces. If a string has embedded spaces, you can do the assignment as follows:

Command

Environment

myname="Sanjiv Guha"

pdksh and bash

set myname="Sanjiv Guha"

tcsh


Accessing Variable Values

You can access the value of a variable by prefixing the variable name with a $ (dollar sign). That is, if the variable name is var, you can access the variable by using $var.

If you want to assign the value of var to the variable lcount, you can do so as follows:

Command

Environment

lcount=$var

pdksh and bash

set lcount=$var

tcsh


Positional Parameters

It is possible to pass options from the command line or from another shell script to your shell program.

These options are supplied to the shell program by Linux as positional parameters, which have special names provided by the system. The first parameter is stored in a variable called 1 (number 1) and can be accessed by using $1 within the program. The second parameter is stored in a variable called 2 and can be accessed by using $2 within the program, and so on. One or more of the higher numbered positional parameters can be omitted while you're invoking a shell program.

Understanding how to use these positional parameters and how to access and use variables retrieved from the command line is necessary when developing more advanced shell programs.

A Simple Example of a Positional Parameter

For example, if a shell program mypgm expects two parameterssuch as a first name and a last nameyou can invoke the shell program with only one parameter, the first name. However, you cannot invoke it with only the second parameter, the last name.

Here is a shell program called mypgm1, which takes only one parameter (a name) and displays it on the screen:

#!/bin/sh #Name display program if [ $# -eq 0 ] then    echo "Name not provided" else    echo "Your name is "$1 fi


If you execute mypgm1, as follows,

$ bash mypgm1


you get the following output:

Name not provided


However, if you execute mypgm1, as follows,

$ bash mypgm1 Sanjiv


you get the following output:

Your name is Sanjiv


The shell program mypgm1 also illustrates another aspect of shell programming: the builtin variables provided to the shell by the Linux kernel. In mypgm1, the built-in variable $# provides the number of positional parameters passed to the shell program. You learn more about working with built-in variables in the next major section of this chapter.

Using Positional Parameters to Access and Retrieve Variables from the Command Line

Using positional parameters in scripts can be helpful if you need to use command lines with piped commands requiring complex arguments. Shell programs containing positional parameters can be even more convenient if the commands are infrequently used. For example, if you use your Fedora system with an attached voice modem as an answering machine, you can write a script to issue a command that retrieves and plays the voice messages. The following lines convert a saved sound file (in .rmd or voice-phone format) and pipe the result to your system's audio device:

#!/bin/sh # play voice message in /var/spool/voice/incoming rmdtopvf /var/spool/voice/incoming/$1 | pvfspeed -s 8000 | \ pvftobasic >/dev/audio


A voice message can then easily be played back using this script (perhaps named pmm):

$ pmm name_of_message


Shell scripts that contain positional parameters are often used for automating routine and mundane jobs, such as system log report generation, file system checks, user resource accounting, printer use accounting, and other system, network, or security administration tasks.

Using a Simple Script to Automate Tasks

You could use a simple script, for example, to examine your system log for certain keywords. If the script is run via your system's scheduling table, /etc/crontab, it can help automate security monitoring. By combining the output capabilities of existing Linux commands with the language facilities of the shell, you can quickly build a useful script to perform a task normally requiring a number of command lines. For example, you can create a short script, named greplog, like this:

#!/bin/sh #    name:  greplog #      use:  mail grep of designated log using keyword # version:  v.01 08aug02 # #  author: bb # # usage: greplog [keyword] [logpathname] # #  bugs: does not check for correct number of arguments # build report name using keyword search and date log_report=/tmp/$1.logreport.'date '+%m%d%y'' # build report header with system type, hostname, date and time echo "==============================================================" \       >$log_report echo "           S Y S T E M   M O N I T O R   L O G" >>$log_report echo uname -a >>$log_report echo "Log report for" 'hostname -f' "on" 'date '+%c''      >>$log_report echo "==============================================================" \       >>$log_report ; echo "" >>$log_report # record log search start echo "Search for->" $1 "starting" 'date '+%r'' >>$log_report echo "" >>$log_report # get and save grep results of keyword ($1) from logfile ($2) grep -i $1 $2 >>$log_report # build report footer with time echo "" >>$log_report echo "End of" $log_report at 'date '+%r'' >>$log_report # mail report to root mail -s "Log Analysis for $1" root <$log_report # clean up and remove report rm $log_report exit 0


In this example, the script creates the variable $log_report, which will be the filename of the temporary report. The keyword ($1) and first argument on the command line is used as part of the filename, along with the current date (with perhaps a better approach to use $$ instead of the date, which appends the script's PID as a file extension). Next, the report header containing some formatted text, the output of the uname command, and the hostname and date is added to the report. The start of the search is then recorded, and any matches of the keyword in the log are added to the report. A footer containing the name of the report and the time is then added. The report is mailed to root with the search term as the subject of the message, and the temporary file is deleted.

Note

By default, Fedora uses the logwatch log monitoring command (actually a Perl script) in your system's /etc/cron.daily directory to generate various reports each day at 0402 (4:02 a.m.). Configure logwatch by editing the file /etc/log.d/logwatch.conf. Other system monitoring tools are included, such as tripwire. System logging can be controlled by editing /etc/syslog.conf.


You can test the script by running it manually and feeding it a keyword and a pathname to the system log, /var/log/messages, like this:

# greplog FAILED /var/log/messages


Note that your system should be running the syslogd daemon. If any login failures have occurred on your system, the root operator might get an email message that looks like this:

Date: Thu, 23 Oct 2003 16:23:24 -0400 From: root <root@stinkpad.home.org> To: root@stinkpad.home.org Subject: FAILED ==============================================================            S Y S T E M   M O N I T O R   L O G Linux stinky 2.4.22-1.2088.nptl #1 Thu Oct 9 20:21:24 EDT 2003 i686 i686 i386 +GNU/Linux Log report for stinkpad.home.org on Thu 23 Oct 2003 04:23:24 PM EDT ============================================================== Search for-> FAILED starting 04:23:24 PM Oct 23 16:23:04 stinkpad login[1769]: FAILED LOGIN 3 FROM (null) FOR bball, +Authentication failure End of /tmp/FAILED.logreport.102303 at 04:23:24 PM


To further automate the process, you can include command lines using the script in another script to generate a series of searches and reports.

Built-in Variables

Built-in variables are special variables provided to shell by Linux that can be used to make decisions within a shell program. You cannot modify the values of these variables within the shell program.

Some of these variables are

$# Number of positional parameters passed to the shell program

$? Completion code of the last command or shell program executed within the shell program (returned value)

$0 The name of the shell program

$* A single string of all arguments passed at the time of invocation of the shell program

To show these built-in variables in use, here is a sample program called mypgm2:

#!/bin/sh #my test program echo "Number of parameters is $#" echo "Program name is $0" echo "Parameters as a single string is $*"


If you execute mypgm2 from the command line in pdksh and bash as follows,

$ bash mypgm2 Sanjiv Guha


you get the following result:

Number of parameters is 2 Program name is mypgm2 Parameters as a single string is Sanjiv Guha


Special Characters

Some characters have special meaning to Linux shells; these characters represent commands, denote specific use for surrounding text, or provide search parameters. Special characters provide a sort of shorthand by incorporating these rather complex meanings into a simple character. Some special characters are shown in Table 15.2.

Table 15.2. Special Shell Characters

Character

Explanation

$

Indicates the beginning of a shell variable name

|

Pipes standard output to next command

#

Starts a comment

&

Executes a process in the background

?

Matches one character

*

Matches one or more characters

>

Output redirection operator

<

Input redirection operator

'

Command substitution (the backquote or backtickthe key above the Tab key on most keyboards)

>>

Output redirection operator (to append to a file)

<<

Wait until following end-of-input string (HERE operator)

[ ]

Range of characters

[a-z]

All characters a through z

[a,z] or [az]

Characters a or z

Space

Delimiter between two words


Special characters are very useful to you when you are creating shell scripts, but if you inadvertently use a special character as part of variable names or strings, your program will behave incorrectly. As you learn in later parts of this section, you can use one of the special characters in a string if you precede it with an escape character (/, or backslash) to indicate that it isn't being used as a special character and shouldn't be treated as such by the program.

A few special characters deserve special note. They are the double quotes ("), the single quotes ('), the backslash (\), and the backtick (')all discussed in the following sections.

Use Double Quotes to Resolve Variables in Strings with Embedded Spaces

If a string contains embedded spaces, you can enclose the string in double quotes (") so that the shell interprets the whole string as one entity instead of more than one.

For example, if you assigned the value of abc def (abc followed by one space, followed by def) to a variable called x in a shell program as follows, you would get an error because the shell would try to execute def as a separate command:

Command

Environment

x=abc def

pdksh and bash

set x=abc def

tcsh


The shell executes the string as a single command if you surround the string in double quotes as follows:

Command

Environment

x="abc def"

pdksh and bash

set x="abc def"

tcsh


The double quotes resolve all variables within the string. Here is an example for pdksh and bash:

var="test string" newvar="Value of var is $var" echo $newvar


Here is the same example for tcsh:

set var="test string" set newvar="Value of var is $var" echo $newvar


If you execute a shell program containing the preceding three lines, you get the following result:

Value of var is test string


Using Single Quotes to Maintain Unexpanded Variables

You can surround a string with single quotes (') to stop the shell from expanding variables and interpreting special characters. When used for the latter purpose, the single quote is an escape character, similar to the backslash, which you learn about in the next section. Here, you learn how to use the single quote to avoid expanding a variable in a shell script. An unexpanded variable maintains its original form in the output.

In the following examples, the double quotes in the preceding examples have been changed to single quotes.

pdksh and bash:

var='test string' newvar='Value of var is $var' echo $newvar


tcsh:

set var = 'test string' set newvar = 'Value of var is $var' echo $newvar


If you execute a shell program containing these three lines, you get the following result: Value of var is $var

As you can see, the variable var maintains its original format in the results, rather than having been expanded.

Using the Backslash As an Escape Character

As you learned earlier, the backslash (\) serves as an escape character that stops the shell from interpreting the succeeding character as a special character. Imagine that you want to assign a value of $test to a variable called var. If you use the following command, the shell reads the special character $ and interprets $test as the value of the variable test. No value has been assigned to test; a null value is stored in var as follows:

Command

Environment

var=$test

pdksh and bash

set var=$test

tcsh


Unfortunately, this assignment might work for bash and pdksh, but it returns an error of undefined variable if you use it with tcsh. Use the following commands to correctly store $test in var:

Command

Environment

var=\$test

pdksh and bash

set var = \$test

tcsh


The backslash before the dollar sign (\$) signals the shell to interpret the $ as any other ordinary character and not to associate any special meaning to it. You could also use single quotes (') around the $test variable to get the same result.

Using the Backtick to Replace a String with Output

You can use the backtick (') character to signal the shell to replace a string with its output when executed. This special character can be used in shell programs when you want the result of the execution of a command to be stored in a variable. For example, if you want to count the number of lines in a file called test.txt in the current directory and store the result in a variable called var, you can use the following command:

Command

Environment

var='wc -l test.txt'

pdksh and bash

set var = 'wc -l test.txt'

tcsh


Comparison of Expressions in pdksh and bash

Comparing values or evaluating the differences between similar bits of datasuch as file information, character strings, or numbersis a task known as comparison of expressions. Comparison of expressions is an integral part of using logic in shell programs to accomplish tasks. The way the logical comparison of two operators (numeric or string) is done varies slightly in different shells. In pdksh and bash, a command called test can be used to achieve comparisons of expressions. In tcsh, you can write an expression to accomplish the same thing.

The following section covers comparison operations using the pdksh or bash shells. Later in the chapter, you learn how to compare expressions in the tcsh shell.

The pdksh and bash shell syntax provide a command named test to compare strings, numbers, and files. The syntax of the test command is as follows:

test expression


or

[ expression ]


Both forms of the test commands are processed the same way by pdksh and bash. The test commands support the following types of comparisons:

  • String comparison

  • Numeric comparison

  • File operators

  • Logical operators

String Comparison

The following operators can be used to compare two string expressions:

= To compare whether two strings are equal

!= To compare whether two strings are not equal

-n To evaluate whether the string length is greater than zero

-z To evaluate whether the string length is equal to zero

Next are some examples using these operators when comparing two strings, string1 and string2, in a shell program called compare1:

#!/bin/sh string1="abc" string2="abd" if [ $string1 = $string2 ]; then    echo "string1 equal to string2" else    echo "string1 not equal to string2" fi if [ $string2 != string1 ]; then    echo "string2 not equal to string1" else    echo "string2 equal to string2" fi if [ $string1 ]; then    echo "string1 is not empty" else    echo "string1 is empty" fi if [ -n $string2 ]; then    echo "string2 has a length greater than zero" else    echo "string2 has length equal to zero" fi if [ -z $string1 ]; then    echo "string1 has a length equal to zero" else    echo "string1 has a length greater than zero" fi


If you execute compare1, you get the following result:

string1 not equal to string2 string2 not equal to string1 string1 is not empty string2 has a length greater than zero string1 has a length greater than zero


If two strings are not equal in size, the system pads out the shorter string with trailing spaces for comparison. That is, if the value of string1 is abc and that of string2 is ab, string2 will be padded with a trailing space for comparison purposesit will have a value of ab.

Number Comparison

The following operators can be used to compare two numbers:

-eq To compare whether two numbers are equal

-ge To compare whether one number is greater than or equal to the other number

-le To compare whether one number is less than or equal to the other number

-ne To compare whether two numbers are not equal

-gt To compare whether one number is greater than the other number

-lt To compare whether one number is less than the other number

The following shell program compares three numbers, number1, number2, and number3:

#!/bin/sh number1=5 number2=10 number3=5 if [ $number1 -eq $number3 ]; then    echo "number1 is equal to number3" else    echo "number1 is not equal to number3" fi if [ $number1 -ne $number2 ]; then    echo "number1 is not equal to number2" else    echo "number1 is equal to number2" fi if [ $number1 -gt $number2 ]; then    echo "number1 is greater than number2" else    echo "number1 is not greater than number2" fi if [ $number1 -ge $number3 ]; then    echo "number1 is greater than or equal to number3" else    echo "number1 is not greater than or equal to number3" fi if [ $number1 -lt $number2 ]; then    echo "number1 is less than number2" else    echo "number1 is not less than number2" fi if [ $number1 -le $number3 ]; then    echo "number1 is less than or equal to number3" else    echo "number1 is not less than or equal to number3" fi


When you execute the shell program, you get the following results:

number1 is equal to number3 number1 is not equal to number2 number1 is not greater than number2 number1 is greater than or equal to number3 number1 is less than number2 number1 is less than or equal to number3


File Operators

The following operators can be used as file comparison operators:

-d To ascertain whether a file is a directory

-f To ascertain whether a file is a regular file

-r To ascertain whether read permission is set for a file

-s To ascertain whether a file exists and has a length greater than zero

-w To ascertain whether write permission is set for a file

-x To ascertain whether execute permission is set for a file

Assume that a shell program called compare3 is in a directory with a file called file1 and a subdirectory dir1 under the current directory. Assume that file1 has a permission of r-x (read and execute permission) and dir1 has a permission of rwx (read, write, and execute permission). The code for the shell program would look like this:

#!/bin/sh if [ -d $dir1 ]; then    echo "dir1 is a directory" else    echo "dir1 is not a directory" fi if [ -f $dir1 ]; then    echo "dir1 is a regular file" else    echo "dir1 is not a regular file" fi if [ -r $file1 ]; then    echo "file1 has read permission" else    echo "file1 does not have read permission" fi if [ -w $file1 ]; then    echo "file1 has write permission" else    echo "file1 does not have write permission" fi if [ -x $dir1 ]; then    echo "dir1 has execute permission" else    echo "dir1 does not have execute permission" fi


If you execute the shell program, you get the following results:

dir1 is a directory file1 is a regular file file1 has read permission file1 does not have write permission dir1 has execute permission


Logical Operators

Logical operators are used to compare expressions using Boolean logic, which compares values using characters representing NOT, AND, and OR.

! To negate a logical expression

-a To logically AND two logical expressions

-o To logically OR two logical expressions

This example named logic uses the file and directory mentioned in the previous compare3 example.

#!/bin/sh if [ -x file1 -a -x dir1 ]; then    echo file1 and dir1 are executable else    echo at least one of file1 or dir1 are not executable fi if [ -w file1 -o -w dir1 ]; then    echo file1 or dir1 are writable else    echo neither file1 or dir1 are executable fi if [ ! -w file1 ]; then    echo file1 is not writable else    echo file1 is writable fi


If you execute logic, it yields the following result:

file1 and dir1 are executable file1 or dir1 are writable file1 is not writable


Comparing Expressions with tcsh

As stated earlier, the method for comparing expressions in tcsh is different from the method used under pdksh and bash. The comparison of expressions demonstrated in this section uses the syntax necessary for the tcsh shell environment.

String Comparison

The following operators can be used to compare two string expressions:

== To compare whether two strings are equal

!= To compare whether two strings are not equal

The following examples compare two strings, string1 and string2, in the shell program compare1:

#!/bin/tcsh set string1 = "abc" set string2 = "abd" if  (string1 == string2) then    echo "string1 equal to string2" else    echo "string1 not equal to string2" endif if  (string2 != string1) then    echo "string2 not equal to string1" else    echo "string2 equal to string1" endif


If you execute compare1, you get the following results:

string1 not equal to string2 string2 not equal to string1


Number Comparison

These operators can be used to compare two numbers:

>= To compare whether one number is greater than or equal to the other number

<= To compare whether one number is less than or equal to the other number

> To compare whether one number is greater than the other number

< To compare whether one number is less than the other number

The next examples compare two numbers, number1 and number2, in a shell program called compare2:

#!/bin/tcsh set number1=5 set number2=10 set number3=5 if  ($number1 > $number2) then    echo "number1 is greater than number2" else    echo "number1 is not greater than number2" endif if  ($number1 >= $number3) then    echo "number1 is greater than or equal to number3" else    echo "number1 is not greater than or equal to number3" endif if  ($number1 < $number2) then    echo "number1 is less than number2" else    echo "number1 is not less than number2" endif if  ($number1 <= $number3) then    echo "number1 is less than or equal to number3" else    echo "number1 is not less than or equal to number3" endif


When executing the shell program compare2, you get the following results:

number1 is not greater than number2 number1 is greater than or equal to number3 number1 is less than number2 number1 is less than or equal to number3


File Operators

These operators can be used as file comparison operators:

-d To ascertain whether a file is a directory

-e To ascertain whether a file exists

-f To ascertain whether a file is a regular file

-o To ascertain whether a user is the owner of a file

-r To ascertain whether read permission is set for a file

-w To ascertain whether write permission is set for a file

-x To ascertain whether execute permission is set for a file

-z To ascertain whether the file size is zero

The following examples are based on a shell program called compare3, which is in a directory with a file called file1 and a subdirectory dir1 under the current directory. Assume that file1 has a permission of r-x (read and execute permission) and dir1 has a permission of rwx (read, write, and execute permission).

The following is the code for the compare3 shell program:

#!/bin/tcsh if (-d dir1) then   echo "dir1 is a directory" else   echo "dir1 is not a directory" endif if (-f dir1) then   echo "file1 is a regular file" else   echo "file1 is not a regular file" endif if (-r file1) then   echo "file1 has read permission" else   echo "file1 does not have read permission" endif if (-w file1) then   echo "file1 has write permission" else   echo "file1 does not have write permission" endif if (-x dir1) then    echo "dir1 has execute permission" else    echo "dir1 does not have execute permission" endif if (-z file1) then    echo "file1 has zero length" else    echo "file1 has greater than zero length" endif


If you execute the file compare3, you get the following results:

dir1 is a directory file1 is a regular file file1 has read permission file1 does not have write permission dir1 has execute permission file1 has greater than zero length


Logical Operators

Logical operators are used with conditional statements. These operators are used to negate a logical expression or to perform logical ANDs and ORs:

! To negate a logical expression

&& To logically AND two logical expressions

|| To logically OR two logical expressions

This example, named logic, uses the file and directory mentioned in the previous compare3 example:

#!/bin/tcsh if ( -x file1 && -x dir1 ) then    echo file1 and dir1 are executable else    echo at least one of file1 or dir1 are not executable endif if ( -w file1 || -w dir1 ) then    echo file1 or dir1 are writable else    echo neither file1 or dir1 are executable endif if ( ! -w file1 ) then    echo file1 is not writable else    echo file1 is writable endif


If you execute logic, it yields the following result:

file1 and dir1 are executable file1 or dir1 are writable file1 is not writable


The for Statement

The for statement is used to execute a set of commands once each time a specified condition is true. The for statement has a number of formats. The first format used by pdksh and bash is as follows:

for curvar in list do     statements done


This form should be used if you want to execute statements once for each value in list. For each iteration, the current value of the list is assigned to vcurvar. list can be a variable containing a number of items or a list of values separated by spaces. The second format is as follows:

for curvar do     statements done


In this form, the statements are executed once for each of the positional parameters passed to the shell program. For each iteration, the current value of the positional parameter is assigned to the variable curvar.

This form can also be written as follows:

for curvar in $@ do     statements done


Remember that $@ gives you a list of positional parameters passed to the shell program, quoted in a manner consistent with the way the user originally invoked the command.

Under tcsh, the for statement is called foreach. The format is as follows:

foreach curvar (list)     statements end


In this form, the statements are executed once for each value in list, and, for each iteration, the current value of list is assigned to curvar.

Suppose that you want to create a backup version of each file in a directory to a subdirectory called backup. You can do the following in pdksh and bash:

#!/bin/sh for filename in * do    cp $filename backup/$filename    if [ $? -ne 0 ]; then       echo "copy for $filename failed"    fi done


In the preceding example, a backup copy of each file is created. If the copy fails, a message is generated.

The same example in tcsh is as follows:

#!/bin/tcsh foreach filename ('/bin/ls')    cp $filename backup/$filename    if ($? != 0) then       echo "copy for $filename failed"    endif end


The while Statement

The while statement can be used to execute a series of commands while a specified condition is true. The loop terminates as soon as the specified condition evaluates to False. It is possible that the loop will not execute at all if the specified condition initially evaluates to False. You should be careful with the while command because the loop will never terminate if the specified condition never evaluates to False.

Endless Loops have their Place in Shell Programs

Endless loops can sometimes be useful. For example, you can easily construct a simple command that constantly monitors the 802.11b link quality of a network interface by using a few lines of script:

#!/bin/sh while :  do    /sbin/iwconfig eth0 | grep Link | tr '\n' '\r'  done


The script outputs the search, and then the tr command formats the output. The result is a simple animation of a constantly updated single line of information:

Link Quality:92/92 Signal level:-11 dBm Noise level:-102 dBm


This technique can also be used to create a graphical monitoring client for X that outputs traffic information and activity about a network interface:

#!/bin/sh xterm -geometry 75x2 -e \ bash -c \ "while :; do \      /sbin/ifconfig eth0 | \      grep 'TX bytes' |       tr '\n' '\r' ; \ done"


The simple example uses a bash command-line script (enabled by -c) to execute a command line repeatedly. The command line pipes the output of the ifconfig command through grep, which searches ifconfig's output and then pipes a line containing the string "TX bytes" to the tr command. The tr command then removes the carriage return at the end of the line to display the information inside an /xterm X11 terminal window, automatically sized by the -geometry option:

RX bytes:4117594780 (3926.8 Mb) TX bytes:452230967 (431.2 Mb)


Endless loops can be so useful that Linux includes a command that repeatedly executes a given command line. For example, you can get a quick report about a system's hardware health by using the sensors command. But instead of using a shell script to loop the output endlessly, you can use the watch command to repeat the information and provide simple animation:

$ watch "sensors -f | cut -c 1-20"



In pdksh and bash, the following format is used for the while flow control construct:

while expression do     statements done


In tcsh, the following format is used:

while (expression)     Statements end


If you want to add the first five even numbers, you can use the following shell program in pdksh and bash:

#!/bin/bash loopcount=0 result=0 while [ $loopcount -lt 5 ] do    loopcount='expr $loopcount + 1'    increment='expr $loopcount \* 2'    result='expr $result + $increment' done echo "result is $result"


In tcsh, this program can be written as follows:

#!/bin/tcsh set loopcount = 0 set result = 0 while ($loopcount < 5)    set loopcount = 'expr $loopcount + 1'    set increment = 'expr $loopcount \* 2'    set result = 'expr $result + $increment' end echo "result is $result"


The until Statement

The until statement can be used to execute a series of commands until a specified condition is true. The loop terminates as soon as the specified condition evaluates to true.

In pdksh and bash, the following format is used:

until expression do     statements done


As you can see, the format of the until statement is similar to that of the while statement, but the logic is different: In a while loop, you execute until an expression is False, but in an until loop, you loop until the expression is true.

If you want to add the first five even numbers, you can use the following shell program in pdksh and bash:

#!/bin/bash loopcount=0 result=0 until [ $loopcount -ge 5 ] do    loopcount='expr $loopcount + 1'    increment='expr $loopcount \* 2'    result='expr $result + $increment' done echo "result is $result"


The example here is identical to the example for the while statement, except that the condition being tested is just the opposite of the condition specified in the while statement.

The tcsh shell does not support the until statement.

The repeat Statement (tcsh)

The repeat statement is used to execute only one command a fixed number of times.

If you want to print a hyphen (-) 80 times with one hyphen per line on the screen, you can use the following command:

repeat 80 echo '-'


The select Statement (pdksh)

The select statement is used to generate a menu list if you are writing a shell program that expects input from the user online. The format of the select statement is as follows:

select item in itemlist do     Statements done


itemlist is optional. If it isn't provided, the system iterates through the item entries one at a time. If itemlist is provided, however, the system iterates for each entry in itemlist and the current value of itemlist is assigned to item for each iteration, which then can be used as part of the statements being executed.

If you want to write a menu that gives the user a choice of picking a Continue or a Finish, you can write the following shell program:

#!/bin/ksh select  item in Continue Finish do    if [ $item = "Finish" ]; then       break    fi done


When the select command is executed, the system displays a menu with numeric choices to the userin this case, 1 for Continue and 2 for Finish. If the user chooses 1, the variable item contains a value of Continue; if the user chooses 2, the variable item contains a value of Finish. When the user chooses 2, the if statement is executed and the loop terminates.

The shift Statement

The shift statement is used to process the positional parameters, one at a time, from left to right. As you'll remember, the positional parameters are identified as $1, $2, $3, and so on. The effect of the shift command is that each positional parameter is moved one position to the left and the current $1 parameter is lost.

The shift statement is useful when you are writing shell programs in which a user can pass various options. Depending on the specified option, the parameters that follow can mean different things or might not be there at all.

The format of the shift command is as follows:

shift number


The parameter number is the number of places to be shifted and is optional. If not specified, the default is 1; that is, the parameters are shifted one position to the left. If specified, the parameters are shifted number positions to the left.

The if Statement

The if statement evaluates a logical expression to make a decision. An if condition has the following format in pdksh and bash:

if [ expression ]; then     Statements elif [ expression ]; then     Statements else     Statements fi


The if conditions can be nested. That is, an if condition can contain another if condition within it. It isn't necessary for an if condition to have an elif or else part. The else part is executed if none of the expressions that are specified in the if statement and are optional in subsequent elif statements are true. The word fi is used to indicate the end of the if statements, which is very useful if you have nested if conditions. In such a case, you should be able to match fi to if to ensure that all if statements are properly coded.

In the following example for bash or pdksh, a variable var can have either of two values: Yes or No. Any other value is invalid. This can be coded as follows:

if [ $var = "Yes" ]; then    echo "Value is Yes" elif [ $var = "No" ]; then    echo "Value is No" else    echo "Invalid value" fi


In tcsh, the if statement has two forms. The first form, similar to the one for pdksh and bash, is as follows:

if (expression) then     Statements else if (expression) then     Statements else     Statements endif


The if conditions can be nestedthat is, an if condition can contain another if condition within it. It isn't necessary for an if condition to have an else part. The else part is executed if none of the expressions specified in any of the if statements are true. The optional if part of the statement (else if (expression) then) is executed if the condition following it is true and the previous if statement is not true. The word endif is used to indicate the end of the if statements, which is very useful if you have nested if conditions. In such a case, you should be able to match endif to if to ensure that all if statements are properly coded.

Using the example of the variable var having only two values, Yes and No, here is how it would be coded with tcsh:

if ($var == "Yes") then    echo "Value is Yes" else if ($var == "No" ) then    echo "Value is No" else    echo "Invalid value" endif


The second form of the if condition for tcsh is as follows:

if (expression) command


In this format, only a single command can be executed if the expression evaluates to true.

The case Statement

The case statement is used to execute statements depending on a discrete value or a range of values matching the specified variable. In most cases, you can use a case statement instead of an if statement if you have a large number of conditions.

The format of a case statement for pdksh and bash is as follows:

case str in    str1 | str2)       Statements;;    str3|str4)       Statements;;    *)       Statements;; esac


You can specify a number of discrete valuessuch as str1, str2, and so onfor each condition, or you can specify a value with a wildcard. The last condition should be * (asterisk) and is executed if none of the other conditions are met. For each of the specified conditions, all the associated statements until the double semicolon (;;) are executed.

You can write a script that will echo the name of the month if you provide the month number as a parameter. If you provide a number that isn't between 1 and 12, you will get an error message. The script is as follows:

#!/bin/sh case $1 in    01 | 1) echo "Month is January";;    02 | 2) echo "Month is February";;    03 | 3) echo "Month is March";;    04 | 4) echo "Month is April";;    05 | 5) echo "Month is May";;    06 | 6) echo "Month is June";;    07 | 7) echo "Month is July";;    08 | 8) echo "Month is August";;    09 | 9) echo "Month is September";;    10) echo "Month is October";;    11) echo "Month is November";;    12) echo "Month is December";;    *) echo "Invalid parameter";; esac


You need to end the statements under each condition with a double semicolon (;;). If you do not, the statements under the next condition will also be executed.

The format for a case statement for tcsh is as follows:

switch (str)    case str1|str2:       Statements       breaksw    case str3|str4:       Statements       breaksw    default:       Statements       breaksw endsw


You can specify a number of discrete valuessuch as str1, str2, and so onfor each condition, or you can specify a value with a wildcard. The last condition should be default and is executed if none of the other conditions is met. For each of the specified conditions, all the associated statements until breaksw are executed.

The example that echoes the month when a number is given, shown earlier for pdksh and bash, can be written in tcsh as follows:

#!/bin/tcsh set month = 5 switch  ( $month )    case 1: echo "Month is January" ; breaksw    case 2: echo "Month is February" ; breaksw    case 3: echo "Month is March" ;  breaksw    case 4: echo "Month is April" ;  breaksw    case 5: echo "Month is May" ;  breaksw    case 6: echo "Month is June" ;  breaksw    case 7: echo "Month is July" ;  breaksw    case 8: echo "Month is August" ;   breaksw    case 9: echo "Month is September" ;  breaksw    case 10: echo "Month is October" ;  breaksw    case 11: echo "Month is November" ;  breaksw    case 12: echo "Month is December" ;  breaksw    default: echo "Oops! Month is Octember!" ;  breaksw endsw


You need to end the statements under each condition with breaksw. If you do not, the statements under the next condition will also be executed.

The break and exit Statements

You should be aware of two other statements: the break statement and the exit statement.

The break statement can be used to terminate an iteration loop, such as a for, until, or repeat command.

exit statements can be used to exit a shell program. You can optionally use a number after exit. If the current shell program has been called by another shell program, the calling program can check for the code (the $? or $status variable, depending on the shell) and make a decision accordingly.

Using Functions in Shell Scripts

As with other programming languages, shell programs also support functions. A function is a piece of a shell program that performs a particular process; you can reuse the same function multiple times within the shell program. Functions help eliminate the need for duplicating code as you write shell programs.

The following is the format of a function in pdksh and bash:

func(){    Statements }


You can call a function as follows:

func param1 param2 param3


The parameters param1, param2, and so on are optional. You can also pass the parameters as a single stringfor example, $@. A function can parse the parameters as if they were positional parameters passed to a shell program from the command line as command-line arguments, but instead use values passed inside the script. For example, the following script uses a function named Displaymonth() that displays the name of the month or an error message if you pass a month number out of the range 1 to 12. This example works with pdksh and bash:

#!/bin/sh Displaymonth() {    case $1 in       01 | 1) echo "Month is January";;       02 | 2) echo "Month is February";;       03 | 3) echo "Month is March";;       04 | 4) echo "Month is April";;       05 | 5) echo "Month is May";;       06 | 6) echo "Month is June";;       07 | 7) echo "Month is July";;       08 | 8) echo "Month is August";;       09 | 9) echo "Month is September";;       10) echo "Month is October";;       11) echo "Month is November";;       12) echo "Month is December";;       *)  echo "Invalid parameter";;    esac } Displaymonth 8


The preceding program displays the following output:

Month is August


Related Fedora and Linux Commands

You can use these commands and tools when using the shell or writing shell scripts:

chsh Command used to change one's login shell

kibitz Allows two-person interaction with a single shell

mc A visual shell named the GNU Midnight Commander

nano An easy-to-use text editor for the console

system-config-users A graphical user-management utility that can be used to change one or more user login shells

shar Command used to create shell archives

vi The vi (actually vim) text editor




Red Hat Fedora 5 Unleashed
Red Hat Fedora 5 Unleashed
ISBN: 067232847X
EAN: 2147483647
Year: 2004
Pages: 362

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net