Bourne Shell Programming

Bourne Shell Programming

This document is also available inLaTeX2e andgzipped PostScript formats.The slides that accompanied the original talk are also available inLaTeX2e andgzipped PostScript formats.

Note: I've noticed that a few university courses' web pageslink to this one. I'm glad that people are finding it useful. Justdon't ask me to do your homework.

Justification

Why bother to learn shell programming, when you could be outthere rollerblading or trying to get a date?

Because, in a word, it's useful.

Many standard utilities (rdist, make,cron, etc.) allow you to specify a command to run at acertain time. Usually, this command is simply passed to the Bourneshell, which means that you can execute whole scripts, should youchoose to do so.

Lastly, Unix runs Bourne shell scripts when it boots. If youwant to modify the boot-time behavior of a system, you need to learnto write and modify Bourne shell scripts.

What's it all about?

First of all, what's a shell? Under Unix, a shell is a commandinterpreter. That is, it reads commands from the keyboard and executesthem.

Furthermore, and this is what this tutorial is all about, youcan put commands in a file and execute them all at once. This is knownas a script. Here's a simple one:

#!/bin/sh
# Rotate procmail log files
cd /homes/arensb/Mail
rm procmail.log.6       # This is redundant
mv procmail.log.5 procmail.log.6
mv procmail.log.4 procmail.log.5
mv procmail.log.3 procmail.log.4
mv procmail.log.2 procmail.log.3
mv procmail.log.1 procmail.log.2
mv procmail.log.0 procmail.log.1
mv procmail.log procmail.log.0

There are several things to note here: first of all, commentsbegin with a hash (#) and continue to the end of the line (thefirst line is special, and we'll cover that in just a moment).

Secondly, the script itself is just a series of commands. Iuse this script to rotate log files, as it says. I could just aseasily have typed these commands in by hand, but I'm lazy, and I don'tfeel like it. Plus, if I did, I might make a typo at the wrong momentand really make a mess.

#!/bin/sh

The first line of any script must begin with #!,followed by the name of the interpreter.

Some versions of Unix allow whitespace between #!and the name of the interpreter. Others don't. Hence, if you want yourscript to be portable, leave out the blank.

A script, like any file that can be run as a command, needs tobe executable: save this script as rotatelog and run

chmod +x rotatelog

to make it executable. You can now run it by running

./rotatelog

Unlike some other operating systems, Unix allows any programto be used as a script interpreter. This is why people talk about ``aBourne shell script'' or ``an awk script.'' One might even write amore script, or an ls script (though the latterwouldn't be terribly useful). Hence, it is important to let Unix knowwhich program will be interpreting the script.

When Unix tries to execute the script, it sees the first twocharacters (#!) and knows that it is a script. It then readsthe rest of the line to find out which program is to execute thescript. For a Bourne shell script, this will be /bin/sh.Hence, the first line of our script must be

#!/bin/sh

After the command interpreter, you can have one, and sometimesmore, options. Some flavors of Unix only allow one, though, so don'tassume that you can have more.

Once Unix has found out which program willbe acting as the interpreter for the script, it runs that program, andpasses it the name of the script on the command line. Thus, when yourun ./rotatelog, it behaves exactly as if you had run/bin/sh ./rotatelog.

Variables

sh allows you to have variables, just like anyprogramming languages. Variables do not need to be declared. To set ash variable, use

VAR= value

and to use the value of the variable later, use

$VAR

or

${VAR}

The latter syntax is useful if the variable name isimmediately followed by other text:

#!/bin/sh
COLOR=yellow

echo This looks $COLORish
echo This seems ${COLOR}ish

prints

This looks
This seems yellowish

There is only one type of variable in sh: strings. This issomewhat limited, but is sufficient for most purposes.

Local vs. environment variables

A sh variable can be either a local variable or anenvironment variable. They both work the same way; the only differencelies in what happens when the script runs another program (which, aswe saw earlier, it does all the time).

Environment variables are passed to subprocesses. Localvariables are not.

By default, variables are local. To turn a local variable intoan environment variable, use

export VAR

Here's a simple wrapper for a program:

#!/bin/sh
NETSCAPE_HOME=/usr/imports/libdata

CLASSPATH=$NETSCAPE_HOME/classes
export CLASSPATH

$NETSCAPE_HOME/bin/netscape.bin

Here, NETSCAPE_HOME is a local variable; CLASSPATHis an environment variable. CLASSPATH will be passed tonetscape.bin (netscape.bin uses the value of thisvariable to find Java class files); NETSCAPE_HOME is aconvenience variable that is only used by the wrapper script;netscape.bin doesn't need to know about it, so it is keptlocal.

The only way to unexport a variable is to unset it:

unset VAR

This removes the variable from the shell's symbol table, effectivelymaking as if it had never existed; as a side effect, the variable isalso unexported.

Also, if you have a function by the same name as the variable, unset will also delete that function.

Since you may want to use this variable later, it is betternot to define it in the first place.

Also, note that if a variable was passed in as part of theenvironment, it is already an environment variable when your scriptstarts running. If there is a variable that you really don't want topass to any subprocesses, you should unset it near the top ofyour script. This is rare, but it might conceivably happen.

If you refer to a variable that hasn't been defined, shsubstitutes the empty string.

#!/bin/sh
echo aaa $FOO bbb
echo xxx${FOO}yyy

prints

aaa bbb
xxxyyy

Special variables

sh treats certain variables specially: some are setfor you when your script runs, and some affect the way commands areinterpreted.

Command-line arguments

The most useful of these variables are the ones referring tothe command-line arguments. $1 refers to the first command-lineargument (after the name of the script), $2 refers to thesecond one, and so forth, up to $9.

If you have more than nine command-line arguments, you can usethe shift command: this discards the first command-lineargument, and bumps the remaining ones up by one position: $2becomes $1, $8 becomes $7, and so forth.

The variable $0 (zero) contains the name of thescript (argv[0] in C programs).

Often, it is useful to just list all of the command-linearguments. For this, sh provides the variables $*(star) and $@ (at). Each of these expands to a stringcontaining all of the command-line arguments, as if you had used$1 $2 $3...

The difference between $* and $@ lies in theway they behave when they occur inside doublequotes: $* behaves in the normal way, whereas $@creates a separate double-quoted string for each command-lineargument. That is, "$*" behaves as if you had written"$1 $2 $3", whereas "$@" behaves as if you hadwritten "$1" "$2" "$3".

Finally, $# contains the number of command-line argumentsthat were given.

Other special variables

$? gives the exit status of the last command that wasexecuted. This should be zero if the command exited normally.

$- lists all of the options with which shwas invoked. See sh(1) for details.

$$ holds the PID of the current process.

$! holds the PID of the last command that wasexecuted in the background.

$IFS (Input Field Separator) determines howsh splits strings into words.

Quasi-variable constructs

The ${VAR} construct is actually a special case of a moregeneral class of constructs:

${VAR :- expression }
Use default value: if VAR is set and non-null, expands to $VAR.Otherwise, expands to expression.
${VAR := expression }
Set default value: if VAR is set and non-null, expands to $VAR.Otherwise, sets VAR to expression and expands to expression.
${VAR :?[ expression] }
If VAR is set and non-null, expands to $VAR. Otherwise, prints expression to standard error and exits with a non-zeroexit status.
${VAR :+ expression }
If VAR is set and non-null, expands to the empty string.Otherwise, expands to expression.
${#VAR }
Expands to the length of $VAR.

The above patterns test whether VAR is set and non-null.Without the colon, they only test whether VAR is set.

Patterns and Globbing

sh supports a limited form ofpattern-matching. The operators are

*
Matches zero or more characters.
?
Matches exactly one character.
[ range ]
Matches any character in range. range can be eithera list of characters that match, or two endpoints separated by a dash: [ak3] matches either a, k, or 3; [a-z] matches any character in the range a through z; [a-mz] matches either a character in the range a through m, or z. If you wish to include adash as part of the range, it must be the first character, e.g., [-p] will match either a dash or p.

When an expression containing these characters occurs in themiddle of a command, sh substitutes the list of all fileswhose name matches the pattern. This is known as ``globbing.''Otherwise, these are used mainly in thecase construct.

As a special case, when a glob begins with * or?, it does not match files that begin with a dot. To matchthese, you need to specify the dot explicitly (e.g.,.*, /tmp/.*).

Note to MS-DOS users: under MS-DOS, the pattern *.*matches every file. In sh, it matches every file thatcontains a dot.

Quoting

If you say something like

echo * MAKE   $$$   FAST *

it won't do what you want: first of all, sh will expand the*s and replace them with a list of all the files in thecurrent directory. Then, since any number of tabs or blanks canseparate words, it will compress the three spaces into one. Finally,it will replace the first instance of $$ with the PID of theshell. This is where quoting comes in.

sh supports several types of quotes. Which one you usedepends on what you want to do.

Backslash

Just as in C strings, a backslash (``\'')removes any special meaning from the character that follows. If thecharacter after the backslash isn't special to begin with, thebackslash has no effect.

The backslash is itself special, so to escape it, just doubleit: \\.

Single quotes

Single quotes, such as

'foo'

work pretty much the way you'd expect: anything inside them (excepta single quote) is quoted. You can say

echo '* MAKE   $$$   FAST *'

and it'll come out the way you want it to.

Note that a backslash inside single quotes also loses itsspecial meaning, so you don't need to double it. There is no way tohave a single quote inside single quotes.

Double quotes

Double quotes, such as

"foo"

preserve spaces and most special characters. However, variables andbackquoted expressions are expanded andreplaced with their value.

Backquotes

If you have an expression within backquotes (also known asbackticks), e.g.,

`cmd`

the expression is evaluated as a command, and replaced with whateverthe expression prints to its standard output. Thus,

echo You are `whoami`

prints

You are arensb

(if you happen to be me, which I do).

Built-in commands

sh understands several built-in commands,i.e., commands that do not correspond to any program. Thesecommands include:

{ commands ; }, ( commands )

Execute commands in a subshell. That is, run them as ifthey were a single command. This is useful when I/O redirection is involved, sinceyou can pipe data to or from a mini-script inside a pipeline.

The { commands; } variant issomewhat more efficient, since it doesn't spawn a truesubshell. This also means that if you set variables inside ofit, the changes will be visible in the rest of the script.

: (colon)
Does nothing. This is generally seen as
: ${VAR := default }
. filename
The dot command reads in the specified filename, as if it hadoccurred at that place in the script.
bg [ job], fg [ job]
bg runs the specified job (or the current job, if noneis specified) in the background. fg resumes thespecified job (or the current job, if none is specified) inthe foreground. Jobs are specified as % number.The jobs command lists jobs.
cd [ dir]
Sets the current directory to dir. If dir is notspecified, sets the current directory to the home directory.
pwd
Prints the current directory.
echo args
Prints args to standard output.
eval args
Evaluates args as a sh expression. This allowsyou to construct a string on the fly ( e.g., using avariable that holds the name of a variable that you want toset) and execute it.
exec command
Runs the specified command, and replaces the current shell withit. That is, nothing after the exec statement will beexecuted, unless the exec itself fails.
exit [ n]
Exit the current shell with exit code n. This defaultsto zero.
kill [ - sig] % job
Send signal sig to the specified job. sig can beeither numeric or symbolic. kill -l lists allavailable signals. By default, sig is SIGTERM (15).
read name...
Reads one line from standard input and assigns it to thevariable name. If several variables name1, name2, name3 etc. are specified, then thefirst word of the line read is assigned to name1, thesecond to name2, and so forth. Any remaining wordsare assigned to the last variable.
set [ +/ - flag] [ arg]

With no arguments, prints the values of all variables.

set -x turns on the x option to sh;set +x turns it off.

set args... sets the command-line arguments toargs.

test expression
Evaluates a boolean expression and exits with an exit code ofzero if it is true, or non-zero if it is false. See test for more details.
trap [ command sig]...
If signal sig is sent to the shell, execute command. This is useful for exiting cleanly( e.g., removing temporary files etc.) when the scriptis interrupted.
ulimit
Print or set system limits on resource usage.
umask [ nnn]
Sets the umask to nnn (an octal number). With no argument,prints the current umask. This is most useful when you want tocreate files, but want to restrict who can read or write them.
wait [ n]
Wait for the background process whose PID is n toterminate. With no arguments, waits for all of the backgroundprocesses to terminate.

Bear in mind that the list of builtins varies from oneimplementation to another, so don't take this list as authoritative.

Flow control

sh supports several flow-control constructs, whichadd power and flexibility to your scripts.

if

The if statement is a simple conditional. You've seenit in every programming language. Its syntax is

if condition ; then
         commands
[ elif condition ; then
         commands]...
[ else
         commands]
fi

That is, an if-block, optionally followed by one or more elif-blocks(elif is short for ``else if''), optionally followed by anelse-block, and terminated by fi.

The if statement pretty much does what you'd expect:if condition is true, it executes the if-block. Otherwise, itexecutes the else-block, if there is one. The elif constructis just syntactic sugar, to let you avoid nesting multiple ifstatements.

#!/bin/sh
myname=`whoami`

if [ $myname = root ]; then
        echo "Welcome to FooSoft 3.0"
else
        echo "You must be root to run this script"
        exit 1
fi

The more observant among you (or those who are math majors)are thinking, ``Hey! You forgot to include the square brackets in thesyntax definition!''

Actually, I didn't: [ is actually a command,/bin/[, and is another name for the test command.See below for details.

This is why you shouldn't call a test program test:if you have ``.'' at the end of your path, as you should, executing test will run /bin/test.

The condition can actually be any command. If itreturns a zero exit status, the condition is true; otherwise, it isfalse. Thus, you can write things like

#!/bin/sh
user=arnie
if grep $user /etc/passwd; then
        echo "$user has an account"
else
        echo "$user doesn't have an account"
fi

while

The while statement should also be familiar to you fromany number of other programming languages. Its syntax in sh is

while condition ; do
         commands
done

As you might expect, the while loop executes commandsas long as condition is true. Again, condition canbe any command, and is true if the command exits with a zero exitstatus.

A while loop may contain two special commands:break and continue.

break exits the while loop immediately,jumping to the next statement after the done.

continue skips the rest of the body of the loop, andjumps back to the top, to where condition is evaluated.

for

The for loop iterates over all of the elements in alist. Its syntax is

for var in list ; do
         commands
done

list is zero or more words. The for constructwill assign the variable var to each word in turn, thenexecute commands. For example:

#!/bin/sh
for i in foo bar baz "do be do"; do
        echo "$i"
done

will print

foo
bar
baz
do be do

A for loop may also contain break andcontinue statements. They work the same way as in thewhile loop.

case

The case construct works like C's switchstatement, except that it matches patterns instead of numericalvalues. Its syntax is

case expression in
         pattern )
                 commands
                 ;;
        ...
esac

expression is a string; this is generally either avariable or a backquoted command.

pattern is a glob pattern (seeglobbing).

The patterns are evaluated in the order in which they areseen, and only the first pattern that matches will be executed. Often,you'll want to include a ``none of the above'' clause; to do this, use* as your last pattern.

I/O redirection

A command's input and/or output may be redirected to anothercommand or to a file. By default, every process has three filedescriptors: standard input (0), standard output (1) and standarderror (2). By default, each of these is connected to the user'sterminal.

However, one can do many interesting things by redirecting oneor more file descriptor:

< filename
Connect standard input to the file filename. This allowsyou to have a command read from the file, rather than havingto type its input in by hand.
> filename
Connect standard output to the file filename. This allowsyou to save the output of a command to a file. If the filedoes not exist, it is created. If it does exist, it is emptiedbefore anything happens.
(Exercise: why doesn't cat * > zzzzzzz work the way you'dexpect?)
>> filename
Connects standard output to the file filename. Unlike >, however, the output of the command is appendedto filename.
<< word

This construct isn't used nearly as often as it could be. Itcauses the command's standard input to come from... standardinput, but only until word appears on a line by itself.Note that there is no space between << andword.

This can be used as a mini-file within a script, e.g.,

cat > foo.c <<EOT
#include <stdio.h>

main()
{
	printf("Hello, world!\n");
}
EOT

It is also useful for printing multiline messages, e.g.:

line=13
cat <<EOT
An error occurred on line $line.
See page 98 of the manual for details.
EOT

As this example shows, by default, << acts like doublequotes (i.e., variables are expanded). If, however,word is quoted, then << acts like singlequotes.

<& digit
Use file descriptor digit as standard input.
>& digit
Use file descriptor digit as standard output.
<&-
Close standard input.
>&-
Close standard output.
command1 | command2

Creates a pipeline: the standard output of command1 isconnected to the standard input of command2. This isfunctionally identical to

command1 > /tmp/foo
command2 < /tmp/foo

except that no temporary file is created, and both commandscan run at the same time

There is a proverb that says, ``A temporary file is just apipe with an attitude and a will to live.''

Any number of commands can be pipelined together.

command1 && command2
Execute command1. Then, if it exited with a zero (true)exit status, execute command2.
command1 || command2
Execute command1. Then, if it exited with a non-zero(false) exit status, execute command2.

If any of the redirection constructs is preceded by a digit,then it applies to the file descriptor with that number, rather thanthe default (0 or 1, as the case may be). For instance,

command 2>&1 > filename

associates file descriptor 2 (standard error) with the same file asfile descriptor 1 (standard output), then redirects both of them tofilename.

This is also useful for printing error messages:

echo "Danger! Danger Will Robinson!" 1>&2

Note that I/O redirections are parsed in the order they areencountered, from left to right. This allows you to do fairly trickythings, including throwing out standard output, and piping standardoutput to a command.

Functions

When a group of commands occurs several times in a script, itis useful to define a function. Defining a function is a lot likecreating a mini-script within a script.

A function is defined using

name () {
         commands
}

and is invoked like any other command:

name args...

You can redirect a function's I/O, embed it in backquotes,etc., just like any other command.

One way in which functions differ from external scripts isthat the shell does not spawn a subshell to execute them. This meansthat if you set a variable inside a function, the new value will bevisible outside of the function.

A function can use return n to terminatewith an exit status of n. Obviously, it can alsoexit n, but that would terminate the entirescript.

Function arguments

A function can take command-line arguments, just like anyscript. Intuitively enough, these are available through $1, $2... $9just like the main script.

Useful utilities

There are a number of commands that aren't part ofsh, but are often used inside sh scripts. Theseinclude:

basename

basename pathname prints the last component ofpathname:

basename /foo/bar/baz

prints

baz

dirname

The complement of basename:dirname pathname prints all but the lastcomponent of pathname, that is the directory part:pathname:

dirname /foo/bar/baz

prints

/foo/bar

[

/bin/[ is another name for /bin/test. Itevaluates its arguments as a boolean expression, and exits with anexit code of 0 if it is true, or 1 if it is false.

If test is invoked as [, then it requires aclosing bracket ] as its last argument. Otherwise, there mustbe no closing bracket.

test understands the following expressions, amongothers:

-e filename
True if filename exists.
-d filename
True if filename exists and is a directory.
-f filename
True if filename exists and is a plain file.
-h filename
True if filename exists and is a symbolic link.
-r filename
True if filename exists and is readable.
-w filename
True if filename exists and is writable.
-n string
True if the length of string is non-zero.
-z string
True if the length of string is zero.
string
True if string is not the empty string.
s1 = s2
True if the strings s1 and s2 are identical.
s1 != s2
True if the strings s1 and s2 are not identical.
n1 -eq n2
True if the numbers n1 and n2 are equal.
n1 -ne n2
True if the numbers n1 and n2 are not equal.
n1 -gt n2
True if the number n1 is greater than n2.
n1 -ge n2
True if the number n1 is greater than or equal to n2.
n1 -lt n2
True if the number n1 is less than n2.
n1 -le n2
True if the number n1 is less than or equal to n2.
! expression
Negates expression, that is, returns true iff expression is false.
expr1 -a expr2
True if both expressions, expr1 and expr2 are true.
expr1 -o expr2
True if either expression, expr1 or expr2 is true.
( expression )
True if expression is true. This allows one to nestexpressions.

Note that lazy evaluation does not apply, since all of thearguments to test are evaluated by sh before beingpassed to test. If you stand to benefit from lazy evaluation,use nested ifs.

echo

echo is a built-in in most implementations ofsh, but it also exists as a standalone command.

echo simply prints its arguments to standard output.It can also be told not to append a newline at the end: under BSD-likeflavors of Unix, use

echo -n " string "

Under SystemV-ish flavors of Unix, use

echo " string \c"

awk

Awk (and its derivatives, nawk and gawk)is a full-fledged scripting language. Inside sh scripts, itis generally used for its ability to split input lines into fields andprint one or more fields. For instance, the following reads/etc/passwd and prints out the name and uid of each user:

awk -F : '{print $1, $3 }' /etc/passwd

The -F : option says that the input records areseparated by colons. By default, awk uses whitespace as thefield separator.

sed

Sed (stream editor) is also a full-fledged scriptinglanguage, albeit a less powerful and more convoluted one thanawk. In sh scripts, sed is mainly used to dostring substitution: the following script reads standard input,replaces all instances of ``foo'' with ``bar'', and writes the resultto standard output:

sed -e 's/foo/bar/g'

The trailing g says to replace all instances of``foo'' with ``bar'' on a line. Without it, only the first instancewould be replaced.

tee

tee [-a] filename reads standardinput, copies it to standard output, and saves a copy in the filefilename.

By default, tee empties filename before itbegins. With the -a option, it appends to filename.

Debugging

Unfortunately, there are no symbolic debuggers such asgdb for sh scripts. When you're debugging a script,you'll have to rely the tried and true method of inserting tracestatements, and using some useful options to sh:

The -n option causes sh to read the scriptbut not execute any commands. This is useful for checking syntax.

The -x option causes sh to print eachcommand to standard error before executing it. Since this can generatea lot of output, you may want to turn tracing on just before thesection that you want to trace, and turn it off immediately afterward:

set -x
# XXX - What's wrong with this code?
grep $user /etc/passwd 1>&2 > /dev/null
set +x

Style

Here follow a few tips on style, as well as one or two tricksthat you may find useful.

Prefer simple, linear application

The advantages of sh are that it is portable (it isfound on every flavor of Unix, and is reasonably standard from oneimplementation to the next), and can do most things that you may wantto do with it. However, it is not particularly fast, and there are nogood debugging tools for sh scripts.

Therefore, it is best to keep things simple and linear: do A,then do B, then do C, and exit. If you find yourself writing manynested loops, or building awk scripts on the fly, you'reprobably better off rewriting it in Perl or C.

Put customization variables at the top

If there's any chance that your script will need to bemodified in a predictable way, then put a customization variable nearthe top of the script. For instance, if you need to run gmake,you might be tempted to write

#!/bin/sh
...300 lines further down...
/usr/local/bin/gmake foo

However, someone else might have gmake installed somewhereelse, so it is better to write

#!/bin/sh
GMAKE=/usr/local/bin/gmake
...300 lines further down...
$GMAKE foo

Don't go overboard with functions

Functions are neat, but sh is not Pascal or C. Inparticular, don't try to encapsulate everything inside a function, andavoid having functions call each other. I once had to debug a scriptwhere the function calls were six deep at times. It wasn't pretty.

Multi-line strings

Remember that you can put newlines in single- or double-quotedstrings. Feel free to use this fact if you need to print out amulti-line error message.

Use : ${VAR:=value} to set defaults

Let's say that your script allows the user to edit a file. Itmight be tempting to include the line

vi $filename

in your script. But let's say that the user prefers to useEmacs as his editor. In this case, he can set $VISUALto indicate his preference.

However,

$VISUAL $filename

is no good either, because $VISUAL might not be set.

So use

: ${VISUAL:=vi}
$VISUAL $filename

to set $VISUAL to a reasonable default, if the user hasn'tset it.

Paranoia

As with any programming language, it is very easy to writesh scripts that don't do what you want, so a healthy dose ofparanoia is a good thing. In particular, scripts that take input fromthe user must be able to handle any kind of input. CGI-bin scriptswill almost certainly be given not only incorrect, but maliciousinput. Errors in scripts that run as root or bin can cause untolddamage as well.

Setuid scripts

DON'T

As we saw above, the way scriptswork, Unix opens the file to find out which program will be the file'sinterpreter. It then invokes the interpeter, and passes it thescript's pathname as a command line argument. The interpreter thenopens the file, reads it, and executes it.

From the above, you can see that there is a delay between whenthe OS opens the script, and when the interpreter opens it. This meansthat there is a race condition that an attacker can exploit: create asymlink that points to the setuid script; then, after the OS hasdetermined the interpeter, but before the interpreter opens the file,replace that symlink with some other script of your choice. Presto!Instant root shell!

This problem is inherent to the way scripts are processed, andtherefore cannot easily be fixed.

Compiled programs do not suffer from this problem, sincea.out (compiled executable) files are not closed then reopened,but directly loaded into memory. Hence, if you have an applicationthat needs to be setuid, but is most easily written as a script, youcan write a wrapper in C that simply exec()s the script. Youstill need to watch out for the usual problems that involve writingsetuid programs, and you have to be paranoid when writing your script,but all of these problems are surmountable. The double-open problem isnot.

$IFS

The very first statement in your script should be

IFS=

which resets the input field separator to its default value.Otherwise, you inherit $IFS from the user, who may have setit to some bizarre value in order to make sh parse stringsdifferently from the way you expect, and induce weird behavior.

$PATH

Right after you set $IFS, make sure you set theexecution path. Otherwise, you inherit it from the user, who may nothave it set to the same value as you do.

In particular, the user might have ``.'' (dot) as the firstelement of his path, and put a program called ls or grepin the current directory, with disastrous results.

In general, never put ``.'' or any other relative directory onyour path.

I like to begin by putting the line

PATH=

at the top of a new script, then add directories to it as necessary(and only add those directories that are necessary).

Quoted variables

Remember that the expansion of a variable might includewhitespace or other special characters, whether accidentally or onpurpose. To guard against this, make sure you double-quote anyvariable that should be interpreted as a single word, or which mightcontain unusual characters (i.e., any user input, andanything derived from that).

I once had a script fail because a user had put a squarebracket in his GCOS field in /etc/passwd. You're best off justquoting everything, unless you know for sure that you shouldn't.

Potentially unset variables

Remember that variables may not be set, or may be set to thenull string. For instance, you may be tempted to write

if [ $answer = yes ]; then

However, $answer might be set to the empty string, sosh would see if [ = yes ]; then,which would cause an error. Better to write

if [ "$answer" = yes ]; then

The danger here is that $answer might be set to-f, so sh would seeif [ -f = yes ]; then, which wouldalso cause an error.

Therefore, write

if [ x"$answer" = xyes ]; then

which avoids both of these problems.

What about the C shell?

The C shell, csh, and its variant tcsh is a fineinteractive shell (I use tcsh), but is a lousy shell forwriting scripts. See Tom Christiansen's article,CshProgramming Considered Harmfulfor the gory details.


Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值