Changes

How to write a shell script

29,577 bytes added, 02:28, 28 June 2007
The following lines were added (+) and removed (-):
           <nowiki>Introduction</nowiki> <nowiki>A Shell script is a program interpreted and executed by the shell, which is essentially a command line interpretor. So, think of a shell script as a list of commands that are run in sequence. This guide covers scripts created for the Borne shell and is based on Reg Quinton's Introduction to Shell Programing. </nowiki> <nowiki></nowiki> <nowiki>Creating a Script</nowiki> <nowiki>Suppose you often type the command </nowiki> <nowiki>    find . -name file -print</nowiki> <nowiki>and you'd rather type a simple command, say </nowiki> <nowiki>    sfind file</nowiki> <nowiki>Create a shell script </nowiki> <nowiki>    % cd ~/bin</nowiki> <nowiki>    % emacs sfind</nowiki> <nowiki>    % page sfind</nowiki> <nowiki>    find . -name $1 -print</nowiki> <nowiki>    % chmod a+x sfind</nowiki> <nowiki>    % rehash</nowiki> <nowiki>    % cd /usr/local/bin</nowiki> <nowiki>    % sfind tcsh</nowiki> <nowiki>    ./shells/tcsh</nowiki> <nowiki>Observations</nowiki> <nowiki>This quick example is far from adequate but some observations: </nowiki> <nowiki>Shell scripts are simple text files created with an editor. </nowiki> <nowiki>Shell scripts are marked as executeable </nowiki> <nowiki>    %chmod a+x sfind</nowiki> <nowiki>Should be located in your search path and ~/bin should be in your search path. </nowiki> <nowiki>You likely need to rehash if you're a Csh (tcsh) user (but not again when you login). </nowiki> <nowiki>Arguments are passed from the command line and referenced. For example, as $1. </nowiki> <nowiki>#!/bin/sh</nowiki> <nowiki>All Bourne Shell scripts should begin with the sequence </nowiki> <nowiki>    #!/bin/sh</nowiki> <nowiki>From the man page for exec(2): </nowiki> <nowiki></nowiki> <nowiki>"On the first line of an interpreter script, following the "#!", is the name of a program which should be used to interpret the contents of the file. For instance, if the first line contains "#! /bin/sh", then the con- tents of the file are executed as a shell script." </nowiki> <nowiki></nowiki> <nowiki>You can get away without this, but you shouldn't. All good scripts state the interpretor explicitly. Long ago there was just one (the Bourne Shell) but these days there are many interpretors -- Csh, Ksh, Bash, and others. </nowiki> <nowiki>Comments</nowiki> <nowiki>Comments are any text beginning with the pound (#) sign. A comment can start anywhere on a line and continue until the end of the line. </nowiki> <nowiki>Search Path</nowiki> <nowiki>All shell scripts should include a search path specifica- tion: </nowiki> <nowiki>    PATH=/usr/ucb:/usr/bin:/bin; export PATH</nowiki> <nowiki>A PATH specification is recommended -- often times a script will fail for some people because they have a different or incomplete search path. </nowiki> <nowiki></nowiki> <nowiki>The Bourne Shell does not export environment variables to children unless explicitly instructed to do so by using the export command. </nowiki> <nowiki>Argument Checking</nowiki> <nowiki>A good shell script should verify that the arguments sup- plied (if any) are correct. </nowiki> <nowiki></nowiki> <nowiki>    if [ $# -ne 3 ]; then</nowiki> <nowiki>        echo 1>&2 Usage: $0 19 Oct 91</nowiki> <nowiki>        exit 127</nowiki> <nowiki>    fi</nowiki> <nowiki>This script requires three arguments and gripes accordingly. </nowiki> <nowiki></nowiki> <nowiki>Exit status</nowiki> <nowiki>All Unix utilities should return an exit status. </nowiki> <nowiki>    # is the year out of range for me?</nowiki> <nowiki></nowiki> <nowiki>    if [ $year -lt 1901  -o  $year -gt 2099 ]; then</nowiki> <nowiki>        echo 1>&2 Year \"$year\" out of range</nowiki> <nowiki>        exit 127</nowiki> <nowiki>    fi</nowiki> <nowiki></nowiki> <nowiki>    etc...</nowiki> <nowiki></nowiki> <nowiki>    # All done, exit ok</nowiki> <nowiki></nowiki> <nowiki>    exit 0</nowiki> <nowiki>A non-zero exit status indicates an error condition of some sort while a zero exit status indicates things worked as expected. </nowiki> <nowiki></nowiki> <nowiki>On BSD systems there's been an attempt to categorize some of the more common exit status codes. See /usr/include/sysexits.h. </nowiki> <nowiki>Using exit status</nowiki> <nowiki>Exit codes are important for those who use your code. Many constructs test on the exit status of a command. </nowiki> <nowiki></nowiki> <nowiki>The conditional construct is: </nowiki> <nowiki>    if command; then</nowiki> <nowiki>        command</nowiki> <nowiki>    fi</nowiki> <nowiki>For example, </nowiki> <nowiki>    if tty -s; then</nowiki> <nowiki>        echo Enter text end with \^D</nowiki> <nowiki>    fi</nowiki> <nowiki>Your code should be written with the expectation that others will use it. Making sure you return a meaningful exit status will help. </nowiki> <nowiki>Stdin, Stdout, Stderr</nowiki> <nowiki>Standard input, output, and error are file descriptors 0, 1, and 2. Each has a particular role and should be used accordingly: </nowiki> <nowiki>    # is the year out of range for me?</nowiki> <nowiki></nowiki> <nowiki>    if [ $year -lt 1901  -o  $year -gt 2099 ]; then</nowiki> <nowiki>        echo 1>&2 Year \"$year\" out of my range</nowiki> <nowiki>        exit 127</nowiki> <nowiki>    fi</nowiki> <nowiki></nowiki> <nowiki>    etc...</nowiki> <nowiki></nowiki> <nowiki>    # ok, you have the number of days since Jan 1, ...</nowiki> <nowiki></nowiki> <nowiki>    case `expr $days % 7` in</nowiki> <nowiki>    0)</nowiki> <nowiki>        echo Mon;;</nowiki> <nowiki>    1)</nowiki> <nowiki>        echo Tue;;</nowiki> <nowiki></nowiki> <nowiki>    etc...</nowiki> <nowiki>Error messages should appear on stderr not on stdout! Output should appear on stdout. As for input/output dialogue: </nowiki> <nowiki>    # give the fellow a chance to quit</nowiki> <nowiki></nowiki> <nowiki>    if tty -s ; then</nowiki> <nowiki>        echo This will remove all files in $* since ...</nowiki> <nowiki>        echo $n Ok to procede? $c;      read ans</nowiki> <nowiki>        case "$ans" in</nowiki> <nowiki>              n*|N*)</nowiki> <nowiki>    echo File purge abandoned;</nowiki> <nowiki>    exit 0  ;;</nowiki> <nowiki>        esac</nowiki> <nowiki>        RM="rm -rfi"</nowiki> <nowiki>    else</nowiki> <nowiki>        RM="rm -rf"</nowiki> <nowiki>    fi</nowiki> <nowiki>Note: this code behaves differently if there's a user to communicate with (ie. if the standard input is a tty rather than a pipe, or file, or etc. See tty(1)). </nowiki> <nowiki>Language Constructs</nowiki> <nowiki>For loop iteration</nowiki> <nowiki></nowiki> <nowiki>Substitute values for variable and perform task: </nowiki> <nowiki>    for variable in word ...</nowiki> <nowiki>    do</nowiki> <nowiki>        command</nowiki> <nowiki>    done</nowiki> <nowiki>For example: </nowiki> <nowiki>    for i in `cat $LOGS`</nowiki> <nowiki>    do</nowiki> <nowiki>            mv $i $i.$TODAY</nowiki> <nowiki>            cp /dev/null $i</nowiki> <nowiki>            chmod 664 $i</nowiki> <nowiki>    done</nowiki> <nowiki>Alternatively you may see: </nowiki> <nowiki>    for variable in word ...; do command; done</nowiki> <nowiki>Case</nowiki> <nowiki></nowiki> <nowiki>Switch to statements depending on pattern match </nowiki> <nowiki>    case word in</nowiki> <nowiki>    [ pattern [ | pattern ... ] )</nowiki> <nowiki>        command ;; ] ...</nowiki> <nowiki>    esac</nowiki> <nowiki>For example: </nowiki> <nowiki></nowiki> <nowiki>    case "$year" in</nowiki> <nowiki></nowiki> <nowiki>    [0-9][0-9])</nowiki> <nowiki>            year=19${year}</nowiki> <nowiki>            years=`expr $year - 1901`</nowiki> <nowiki>            ;;</nowiki> <nowiki>    [0-9][0-9][0-9][0-9])</nowiki> <nowiki>            years=`expr $year - 1901`</nowiki> <nowiki>            ;;</nowiki> <nowiki>    *)</nowiki> <nowiki>            echo 1>&2 Year \"$year\" out of range ...</nowiki> <nowiki>            exit 127</nowiki> <nowiki>            ;;</nowiki> <nowiki>    esac</nowiki> <nowiki>Conditional Execution</nowiki> <nowiki></nowiki> <nowiki>Test exit status of command and branch </nowiki> <nowiki>    if command</nowiki> <nowiki>    then</nowiki> <nowiki>        command</nowiki> <nowiki>    [ else</nowiki> <nowiki>        command ]</nowiki> <nowiki>    fi</nowiki> <nowiki>For example: </nowiki> <nowiki>    if [ $# -ne 3 ]; then</nowiki> <nowiki>            echo 1>&2 Usage: $0 19 Oct 91</nowiki> <nowiki>            exit 127</nowiki> <nowiki>    fi</nowiki> <nowiki>Alternatively you may see: </nowiki> <nowiki>    if command; then command; [ else command; ] fi</nowiki> <nowiki>While/Until Iteration</nowiki> <nowiki></nowiki> <nowiki>Repeat task while command returns good exit status. </nowiki> <nowiki>    {while | until} command</nowiki> <nowiki>    do</nowiki> <nowiki>        command</nowiki> <nowiki>    done</nowiki> <nowiki>For example: </nowiki> <nowiki>    # for each argument mentioned, purge that directory</nowiki> <nowiki></nowiki> <nowiki>    while [ $# -ge 1 ]; do</nowiki> <nowiki>            _purge $1</nowiki> <nowiki>            shift</nowiki> <nowiki>    done</nowiki> <nowiki>Alternatively you may see: </nowiki> <nowiki>    while command; do command; done</nowiki> <nowiki>Variables</nowiki> <nowiki></nowiki> <nowiki>Variables are sequences of letters, digits, or underscores beginning with a letter or underscore. To get the contents of a variable you must prepend the name with a $. </nowiki> <nowiki></nowiki> <nowiki>Numeric variables (eg. like $1, etc.) are positional vari- ables for argument communication. </nowiki> <nowiki></nowiki> <nowiki>Variable Assignment</nowiki> <nowiki></nowiki> <nowiki>Assign a value to a variable by variable=value. For example: </nowiki> <nowiki>    PATH=/usr/ucb:/usr/bin:/bin; export PATH</nowiki> <nowiki>or </nowiki> <nowiki>    TODAY=`(set \`date\`; echo $1)`</nowiki> <nowiki>Exporting Variables</nowiki> <nowiki></nowiki> <nowiki>Variables are not exported to children unless explicitly marked. </nowiki> <nowiki>    # We MUST have a DISPLAY environment variable</nowiki> <nowiki></nowiki> <nowiki>    if [ "$DISPLAY" = "" ]; then</nowiki> <nowiki>            if tty -s ; then</nowiki> <nowiki>    echo "DISPLAY (`hostname`:0.0)? \c";</nowiki> <nowiki>    read DISPLAY</nowiki> <nowiki>            fi</nowiki> <nowiki>            if [ "$DISPLAY" = "" ]; then</nowiki> <nowiki>    DISPLAY=`hostname`:0.0</nowiki> <nowiki>            fi</nowiki> <nowiki>            export DISPLAY</nowiki> <nowiki>    fi</nowiki> <nowiki>Likewise, for variables like the PRINTER which you want hon- ored by lpr(1). From a user's .profile: </nowiki> <nowiki>    PRINTER=PostScript; export PRINTER</nowiki> <nowiki>Note: that the Cshell exports all environment variables. </nowiki> <nowiki></nowiki> <nowiki>Referencing Variables</nowiki> <nowiki></nowiki> <nowiki>Use $variable (or, if necessary, ${variable}) to reference the value. </nowiki> <nowiki>    # Most user's have a /bin of their own</nowiki> <nowiki></nowiki> <nowiki>    if [ "$USER" != "root" ]; then</nowiki> <nowiki>            PATH=$HOME/bin:$PATH</nowiki> <nowiki>    else</nowiki> <nowiki>            PATH=/etc:/usr/etc:$PATH</nowiki> <nowiki>    fi</nowiki> <nowiki>The braces are required for concatenation constructs. </nowiki> <nowiki>$p_01</nowiki> <nowiki>The value of the variable "p_01". </nowiki> <nowiki>${p}_01</nowiki> <nowiki>The value of the variable "p" with "_01" pasted onto the end. </nowiki> <nowiki></nowiki> <nowiki>Conditional Reference</nowiki> <nowiki></nowiki> <nowiki>${variable-word}</nowiki> <nowiki>If the variable has been set, use it's value, else use word. </nowiki> <nowiki>POSTSCRIPT=${POSTSCRIPT-PostScript};</nowiki> <nowiki>export POSTSCRIPT</nowiki> <nowiki></nowiki> <nowiki>${variable:-word}</nowiki> <nowiki>If the variable has been set and is not null, use it's value, else use word. </nowiki> <nowiki></nowiki> <nowiki>These are useful constructions for honoring the user envi- ronment. Ie. the user of the script can override variable assignments. Cf. programs like lpr(1) honor the PRINTER environment variable, you can do the same trick with your shell scripts. </nowiki> <nowiki>${variable:?word}</nowiki> <nowiki>If variable is set use it's value, else print out word and exit. Useful for bailing out. </nowiki> <nowiki></nowiki> <nowiki>Arguments</nowiki> <nowiki></nowiki> <nowiki>Command line arguments to shell scripts are positional vari- ables: </nowiki> <nowiki>$0, $1, ...</nowiki> <nowiki>The command and arguments. With $0 the command and the rest the arguments. </nowiki> <nowiki>$#</nowiki> <nowiki>The number of arguments. </nowiki> <nowiki>$*, $@</nowiki> <nowiki>All the arguments as a blank separated string. Watch out for "$*" vs. "$@". </nowiki> <nowiki>And, some commands: </nowiki> <nowiki>shift</nowiki> <nowiki>Shift the postional variables down one and decrement number of arguments. </nowiki> <nowiki>set arg arg ...</nowiki> <nowiki>Set the positional variables to the argument list. </nowiki> <nowiki></nowiki> <nowiki>Command line parsing uses shift: </nowiki> <nowiki>    # parse argument list</nowiki> <nowiki></nowiki> <nowiki>    while [ $# -ge 1 ]; do</nowiki> <nowiki>            case $1 in</nowiki> <nowiki>        process arguments...</nowiki> <nowiki>            esac</nowiki> <nowiki>            shift</nowiki> <nowiki>    done</nowiki> <nowiki>A use of the set command: </nowiki> <nowiki>    # figure out what day it is</nowiki> <nowiki></nowiki> <nowiki>    TODAY=`(set \`date\`; echo $1)`</nowiki> <nowiki></nowiki> <nowiki>    cd $SPOOL</nowiki> <nowiki></nowiki> <nowiki>    for i in `cat $LOGS`</nowiki> <nowiki>    do</nowiki> <nowiki>            mv $i $i.$TODAY</nowiki> <nowiki>            cp /dev/null $i</nowiki> <nowiki>            chmod 664 $i</nowiki> <nowiki>    done</nowiki> <nowiki>Special Variables </nowiki> <nowiki>$$</nowiki> <nowiki>Current process id. This is very useful for constructing temporary files. </nowiki> <nowiki>        tmp=/tmp/cal0$$</nowiki> <nowiki>        trap "rm -f $tmp /tmp/cal1$$ /tmp/cal2$$"</nowiki> <nowiki>        trap exit 1 2 13 15</nowiki> <nowiki>        /usr/lib/calprog >$tmp</nowiki> <nowiki></nowiki> <nowiki>$?</nowiki> <nowiki>The exit status of the last command. </nowiki> <nowiki>        $command</nowiki> <nowiki>        # Run target file if no errors and ...</nowiki> <nowiki></nowiki> <nowiki>        if [ $? -eq 0 ]</nowiki> <nowiki>        then</nowiki> <nowiki>  etc...</nowiki> <nowiki>        fi</nowiki> <nowiki></nowiki> <nowiki>Quotes/Special Characters</nowiki> <nowiki></nowiki> <nowiki>Special characters to terminate words: </nowiki> <nowiki>      ; & ( ) | ^ < > new-line space tab</nowiki> <nowiki>These are for command sequences, background jobs, etc. To quote any of these use a backslash (\) or bracket with quote marks ("" or '').</nowiki> <nowiki></nowiki> <nowiki>Single Quotes</nowiki> <nowiki></nowiki> <nowiki>Within single quotes all characters are quoted -- including the backslash. The result is one word. </nowiki> <nowiki></nowiki> <nowiki>        grep :${gid}: /etc/group | awk -F: '{print $1}'</nowiki> <nowiki>Double Quotes</nowiki> <nowiki></nowiki> <nowiki>Within double quotes you have variable subsitution (ie. the dollar sign is interpreted) but no file name generation (ie. * and ? are quoted). The result is one word. </nowiki> <nowiki>        if [ ! "${parent}" ]; then</nowiki> <nowiki>          parent=${people}/${group}/${user}</nowiki> <nowiki>        fi</nowiki> <nowiki>Back Quotes</nowiki> <nowiki></nowiki> <nowiki>Back quotes mean run the command and substitute the output. </nowiki> <nowiki></nowiki> <nowiki>        if [ "`echo -n`" = "-n" ]; then</nowiki> <nowiki>        n=""</nowiki> <nowiki>        c="\c"</nowiki> <nowiki>        else</nowiki> <nowiki>        n="-n"</nowiki> <nowiki>        c=""</nowiki> <nowiki>        fi</nowiki> <nowiki>and </nowiki> <nowiki>        TODAY=`(set \`date\`; echo $1)`</nowiki> <nowiki>Functions</nowiki> <nowiki></nowiki> <nowiki>Functions are a powerful feature that aren't used often enough. Syntax is </nowiki> <nowiki>    name ()</nowiki> <nowiki>    {</nowiki> <nowiki>        commands</nowiki> <nowiki>    }</nowiki> <nowiki>For example: </nowiki> <nowiki></nowiki> <nowiki>    # Purge a directory</nowiki> <nowiki></nowiki> <nowiki>    _purge()</nowiki> <nowiki>    {</nowiki> <nowiki>            # there had better be a directory</nowiki> <nowiki></nowiki> <nowiki>            if [ ! -d $1 ]; then</nowiki> <nowiki>    echo $1: No such directory 1>&2</nowiki> <nowiki>    return</nowiki> <nowiki>            fi</nowiki> <nowiki></nowiki> <nowiki>        etc...</nowiki> <nowiki>    }</nowiki> <nowiki>Within a function the positional parmeters $0, $1, etc. are the arguments to the function (not the arguments to the script). </nowiki> <nowiki></nowiki> <nowiki>Within a function use return instead of exit. </nowiki> <nowiki></nowiki> <nowiki>Functions are good for encapsulations. You can pipe, redi- rect input, etc. to functions. For example: </nowiki> <nowiki>    # deal with a file, add people one at a time</nowiki> <nowiki></nowiki> <nowiki>    do_file()</nowiki> <nowiki>    {</nowiki> <nowiki>            while parse_one</nowiki> <nowiki></nowiki> <nowiki>            etc...</nowiki> <nowiki>    }</nowiki> <nowiki></nowiki> <nowiki>    etc...</nowiki> <nowiki></nowiki> <nowiki>    # take standard input (or a specified file) and do it.</nowiki> <nowiki></nowiki> <nowiki>    if [ "$1" != "" ]; then</nowiki> <nowiki>            cat $1 | do_file</nowiki> <nowiki>    else</nowiki> <nowiki>            do_file</nowiki> <nowiki>    fi</nowiki> <nowiki>Sourcing commands</nowiki> <nowiki></nowiki> <nowiki>You can execute shell scripts from within shell scripts. A couple of choices: </nowiki> <nowiki></nowiki> <nowiki>sh command</nowiki> <nowiki></nowiki> <nowiki>This runs the shell script as a separate shell. For example, on Sun machines in /etc/rc: </nowiki> <nowiki>        sh /etc/rc.local</nowiki> <nowiki>. command</nowiki> <nowiki></nowiki> <nowiki>This runs the shell script from within the current shell script. For example: </nowiki> <nowiki>        # Read in configuration information</nowiki> <nowiki>        .  /etc/hostconfig</nowiki> <nowiki>What are the virtues of each? What's the difference? The second form is useful for configuration files where environment variable are set for the script. For example: </nowiki> <nowiki>    for HOST in $HOSTS; do</nowiki> <nowiki></nowiki> <nowiki>      # is there a config file for this host?</nowiki> <nowiki></nowiki> <nowiki>      if [ -r ${BACKUPHOME}/${HOST} ]; then</nowiki> <nowiki>.  ${BACKUPHOME}/${HOST}</nowiki> <nowiki>      fi</nowiki> <nowiki>    etc...</nowiki> <nowiki>Using configuration files in this manner makes it possible to write scripts that are automatically tailored for differ- ent situations. </nowiki> <nowiki>Some Tricks</nowiki> <nowiki>Test</nowiki> <nowiki></nowiki> <nowiki>The most powerful command is test(1). </nowiki> <nowiki>    if test expression; then</nowiki> <nowiki></nowiki> <nowiki>        etc...</nowiki> <nowiki>and (note the matching bracket argument) </nowiki> <nowiki>    if [ expression ]; then</nowiki> <nowiki></nowiki> <nowiki>        etc...</nowiki> <nowiki>On System V machines this is a builtin (check out the com- mand /bin/test). </nowiki> <nowiki></nowiki> <nowiki>On BSD systems (like the Suns) compare the command /usr/bin/test with /usr/bin/[. </nowiki> <nowiki></nowiki> <nowiki>Useful expressions are: </nowiki> <nowiki>test { -w, -r, -x, -s, ... } filename</nowiki> <nowiki>is file writeable, readable, executeable, empty, etc? </nowiki> <nowiki>test n1 { -eq, -ne, -gt, ... } n2</nowiki> <nowiki>are numbers equal, not equal, greater than, etc.? </nowiki> <nowiki>test s1 { =, != } s2</nowiki> <nowiki>Are strings the same or different? </nowiki> <nowiki>test cond1 { -o, -a } cond2</nowiki> <nowiki>Binary or; binary and; use ! for unary negation. </nowiki> <nowiki></nowiki> <nowiki>For example </nowiki> <nowiki>    if [ $year -lt 1901  -o  $year -gt 2099 ]; then</nowiki> <nowiki>        echo 1>&2 Year \"$year\" out of range</nowiki> <nowiki>        exit 127</nowiki> <nowiki>    fi</nowiki> <nowiki>Learn this command inside out! It does a lot for you. </nowiki> <nowiki></nowiki> <nowiki>String matching</nowiki> <nowiki></nowiki> <nowiki>The test command provides limited string matching tests. A more powerful trick is to match strings with the case switch. </nowiki> <nowiki>    # parse argument list</nowiki> <nowiki></nowiki> <nowiki>    while [ $# -ge 1 ]; do</nowiki> <nowiki>            case $1 in</nowiki> <nowiki>            -c*)    rate=`echo $1 | cut -c3-`;;</nowiki> <nowiki>            -c)    shift;  rate=$1 ;;</nowiki> <nowiki>            -p*)    prefix=`echo $1 | cut -c3-`;;</nowiki> <nowiki>            -p)    shift;  prefix=$1 ;;</nowiki> <nowiki>            -*)    echo $Usage; exit 1 ;;</nowiki> <nowiki>            *)      disks=$*;      break  ;;</nowiki> <nowiki>            esac</nowiki> <nowiki></nowiki> <nowiki>            shift</nowiki> <nowiki></nowiki> <nowiki>    done</nowiki> <nowiki>Of course getopt would work much better. </nowiki> <nowiki></nowiki> <nowiki>SysV vs BSD echo</nowiki> <nowiki></nowiki> <nowiki>On BSD systems to get a prompt you'd say: </nowiki> <nowiki>    echo -n Ok to procede?;  read ans</nowiki> <nowiki>On SysV systems you'd say: </nowiki> <nowiki>    echo Ok to procede? \c; read ans</nowiki> <nowiki>In an effort to produce portable code we've been using: </nowiki> <nowiki>    # figure out what kind of echo to use</nowiki> <nowiki></nowiki> <nowiki>    if [ "`echo -n`" = "-n" ]; then</nowiki> <nowiki>            n="";  c="\c"</nowiki> <nowiki>    else</nowiki> <nowiki>            n="-n";    c=""</nowiki> <nowiki>    fi</nowiki> <nowiki></nowiki> <nowiki>    etc...</nowiki> <nowiki></nowiki> <nowiki>    echo $n Ok to procede? $c; read ans</nowiki> <nowiki>Is there a person?</nowiki> <nowiki></nowiki> <nowiki>The Unix tradition is that programs should execute as qui- etly as possible. Especially for pipelines, cron jobs, etc. </nowiki> <nowiki></nowiki> <nowiki>User prompts aren't required if there's no user. </nowiki> <nowiki>    # If there's a person out there, prod him a bit.</nowiki> <nowiki></nowiki> <nowiki>    if tty -s; then</nowiki> <nowiki>        echo Enter text end with \^D</nowiki> <nowiki>    fi</nowiki> <nowiki>The tradition also extends to output. </nowiki> <nowiki>    # If the output is to a terminal, be verbose</nowiki> <nowiki></nowiki> <nowiki>    if tty -s <&1; then</nowiki> <nowiki>        verbose=true</nowiki> <nowiki>    else</nowiki> <nowiki>        verbose=false</nowiki> <nowiki>    fi</nowiki> <nowiki>Beware: just because stdin is a tty that doesn't mean that stdout is too. User prompts should be directed to the user terminal. </nowiki> <nowiki>    # If there's a person out there, prod him a bit.</nowiki> <nowiki></nowiki> <nowiki>    if tty -s; then</nowiki> <nowiki>        echo Enter text end with \^D >&0</nowiki> <nowiki>    fi</nowiki> <nowiki>Have you ever had a program stop waiting for keyboard input when the output is directed elsewhere? </nowiki> <nowiki></nowiki> <nowiki>Creating Input</nowiki> <nowiki></nowiki> <nowiki>We're familiar with redirecting input. For example: </nowiki> <nowiki>    # take standard input (or a specified file) and do it.</nowiki> <nowiki></nowiki> <nowiki>    if [ "$1" != "" ]; then</nowiki> <nowiki>            cat $1 | do_file</nowiki> <nowiki>    else</nowiki> <nowiki>            do_file</nowiki> <nowiki>    fi</nowiki> <nowiki>alternatively, redirection from a file: </nowiki> <nowiki>    # take standard input (or a specified file) and do it.</nowiki> <nowiki></nowiki> <nowiki>    if [ "$1" != "" ]; then</nowiki> <nowiki>            do_file < $1</nowiki> <nowiki>    else</nowiki> <nowiki>            do_file</nowiki> <nowiki>    fi</nowiki> <nowiki>You can also construct files on the fly. </nowiki> <nowiki>    rmail bsmtp <</nowiki> <nowiki>    rcpt to:</nowiki> <nowiki>    data</nowiki> <nowiki>    from: <$1@newshost.uwo.ca></nowiki> <nowiki>    to: </nowiki> <nowiki>    Subject: Signon $2</nowiki> <nowiki></nowiki> <nowiki>    subscribe $2 Usenet Feeder at UWO</nowiki> <nowiki>    .</nowiki> <nowiki>    quit</nowiki> <nowiki>    EOF</nowiki> <nowiki>Note: that variables are expanded in the input. </nowiki> <nowiki></nowiki> <nowiki>String Manipulations</nowiki> <nowiki></nowiki> <nowiki>One of the more common things you'll need to do is parse strings. Some tricks </nowiki> <nowiki></nowiki> <nowiki>    TIME=`date | cut -c12-19`</nowiki> <nowiki></nowiki> <nowiki>    TIME=`date | sed 's/.* .* .* \(.*\) .* .*/\1/'`</nowiki> <nowiki></nowiki> <nowiki>    TIME=`date | awk '{print $4}'`</nowiki> <nowiki></nowiki> <nowiki>    TIME=`set \`date\`; echo $4`</nowiki> <nowiki></nowiki> <nowiki>    TIME=`date | (read u v w x y z; echo $x)`</nowiki> <nowiki>With some care, redefining the input field separators can help. </nowiki> <nowiki></nowiki> <nowiki>    #!/bin/sh</nowiki> <nowiki>    # convert IP number to in-addr.arpa name</nowiki> <nowiki></nowiki> <nowiki>    name()</nowiki> <nowiki>    {    set `IFS=".";echo $1`</nowiki> <nowiki>        echo $4.$3.$2.$1.in-addr.arpa</nowiki> <nowiki>    }</nowiki> <nowiki></nowiki> <nowiki>    if [ $# -ne 1 ]; then</nowiki> <nowiki>        echo 1>&2 Usage: bynum IP-address</nowiki> <nowiki>        exit 127</nowiki> <nowiki>    fi</nowiki> <nowiki></nowiki> <nowiki>    add=`name $1`</nowiki> <nowiki></nowiki> <nowiki>    nslookup < < EOF | grep "$add" | sed 's/.*= //'</nowiki> <nowiki>    set type=any</nowiki> <nowiki>    $add</nowiki> <nowiki>    EOF</nowiki> <nowiki>Pattern Matching</nowiki> <nowiki></nowiki> <nowiki>There are two kinds of pattern matching available, matching from the left and matching from the right. The operators, with their functions. Operator      Function        Example</nowiki> <nowiki>${foo#t*is}    deletes the shortest possible match from the left      export $foo="this is a test"</nowiki> <nowiki>echo ${foo#t*is}</nowiki> <nowiki>is a test </nowiki> <nowiki>${foo##t*is}  deletes the longest possible match from the left        export $foo="this is a test"</nowiki> <nowiki>echo ${foo#t*is}</nowiki> <nowiki>a test </nowiki> <nowiki>${foo%t*st}    deletes the shortest possible match from the right      export $foo="this is a test" </nowiki> <nowiki>echo ${foo%t*st}</nowiki> <nowiki>this is a </nowiki> <nowiki>${foo%%t*st}  deletes the longest possible match from the right      export $foo="this is a test"</nowiki> <nowiki>echo ${foo#t*is}</nowiki> <nowiki>  </nowiki> <nowiki></nowiki> <nowiki>Substitution</nowiki> <nowiki></nowiki> <nowiki>Another kind of variable mangling you might want to employ is substitution. There are four substitution operators in Bash. Operator        Function        Example</nowiki> <nowiki>${foo:-bar}    If $foo exists and is not null, return $foo. If it doesn't exist, or is null, return bar.      export foo=""</nowiki> <nowiki>echo ${foo:-one}</nowiki> <nowiki>one</nowiki> <nowiki>echo $foo</nowiki> <nowiki>  </nowiki> <nowiki>${foo:=bar}    If $foo exists and is not null, return $foo. If it doesn't exist, or is null, set $foo to bar and return barexport foo=""</nowiki> <nowiki>echo ${foo:=one}</nowiki> <nowiki>one</nowiki> <nowiki>echo $foo</nowiki> <nowiki>one </nowiki> <nowiki>${foo:+bar}    If $foo exists and is not null, return bar. If it doesn't exist, or is null, return a null.    export foo="this is a test" </nowiki> <nowiki>echo ${foo:+bar}</nowiki> <nowiki>bar </nowiki> <nowiki>${foo:?"error message"}        If $foo exists and isn't null, return it's value. If it doesn't exist, or is null, print the error message. If no error message is given, it prints parameter null or not set.</nowiki> <nowiki>Note: In a non-interactive shell, this will abort the current script. In an interactive shell, this will just print the error message.    export foo="one"</nowiki> <nowiki>for i in foo bar baz; do</nowiki> <nowiki>eval echo \${$foo:?}</nowiki> <nowiki>one</nowiki> <nowiki>bash: bar: parameter null or not set</nowiki> <nowiki>bash: baz: parameter null or not set</nowiki> <nowiki></nowiki> <nowiki>Debugging</nowiki> <nowiki></nowiki> <nowiki>The shell has a number of flags that make debugging easier: </nowiki> <nowiki></nowiki> <nowiki>sh -n command</nowiki> <nowiki></nowiki> <nowiki>Read the shell script but don't execute the commands. IE. check syntax. </nowiki> <nowiki></nowiki> <nowiki>sh -x command</nowiki> <nowiki></nowiki> <nowiki>Display commands and arguments as they're executed. In a lot of my shell scripts you'll see </nowiki> <nowiki>    # Uncomment the next line for testing</nowiki> <nowiki>    # set -x</nowiki> <nowiki></nowiki> <nowiki></nowiki>
Bureaucrat, administrator
16,195
edits