Shell Scripting Tips
(revisited. Again)

Nick Holland 11/21/2023

Target Audience

Keep it neat and simple

Classes of scripts

Deal with errors

Minimize errors

-e : Trap all errors!

#!/bin/ksh -e

cd /some/dir
rm -r *   # Could be bad if the 'cd' failed!
  • Big hammer. Often too big.
  • (though...IF you handle "ALL possible" errors, -e can work)
  • Header for Q&D scripts?

    #!/bin/ksh
    yell() { echo "$0: $*" >&2; }
    die()  { yell "$*" ; exit 111; }
    try()  { "$@" || die "cannot $*"; }
    
    try cd /some/dir
    rm -r *   # Could be bad if the 'cd' failed!
    
     
    $ ./test.ksh   
    ./test.ksh[7]: cd: /some/dir - No such file or directory
    ./test.ksh: cannot cd /some/dir
    
  • A more selective hammer, but still...a hammer
  • but possibly what you need for Q&D.
  • Better Header for Q&D scripts?

    #!/bin/ksh
    MYTMP=$(mktmp -d /tmp/tmpdata.XXXXXXXX) || exit 254
    yell() { echo "$0: $*" >&2; }
    die()  { yell "$*" ; exit 111; }
    try()  { "$@" || die "cannot $*"; }
    
    function cleanexit () {
        if [[ -n $MYTMP ]]; then
    	rm $MYTMP/*
    	rmdir $MYTMP
        fi
        exit
    }
      [ ... your code here ... ]
    
    cleanexit
    

    Footer line for all reports:

    Build a shell script template!

  • try/yell/die
  • Site specific stuff
  • run-as user code
  • --> and comments about how to use it
  • /tmp file cleanup
  • footer line
  • Comment out the "irregular" stuff. Delete it if you are not going to use it in a script.
  • Validate all input

  • Don't try to remove undesired things. Only accept things you expect.
  • "tr" is your friend -- "-d" (delete) and "-c" (complement).
  • tr -dc "valid-characters"
  • INPUT=$(echo "$INPUT"|tr -dc "a-zA-Z_0-9-")
  • definitely good for user-facing code
  • Never let users enter data directly into a data file!
  • What if they entered "$(reboot)"?
  • Make friends with set, typeset

    set -f -- Turn off globbing!

    $ A="*"
    $ echo $A
    Desktop Downloads ShellScriptTips.odp ShellScriptTips01.odp
       [...] 
    $ set -f  
    $ A="*"
    $ echo $A
    *
    $ 
    
  • don't forget if you are turning globbing off!
  • set -- $V -- put $V into $*

    $ A="     A    b  c D   e    "
    $ echo "$A"
         A    b  c D   e    
    $ echo "$*"
    
    $ set -- $A
    $ echo "$*" 
    A b c D e
    $ echo "$3"
    c
    

    Case insensitivity: tr(1)

    $ V="AbCdEf"
    $ V=$(echo $V | tr "A-Z" "a-z")
    $ echo $V
    abcdef
    $ 
    

    Case insensitivity: the lazy way

    $ typeset -l V
    $ V="AbCdEf" ; echo $V
    abcdef
    $ typeset -u V         
    $ V="AbCdEf" ; echo $V 
    ABCDEF
    

    tput(1)

    BOLD=$(tput bold)
    NORM=$(tput sgr0)
    echo -n "Enter your input ->$BOLD"
    read response
    echo -n "$NORM"
    
    Lots of other options, varying degrees of portability, however.

    General Tips(using KSH!)

    ^^^ From Robert Peichaer (rpe@openbsd)

    Commenting complex pipelines

    diff -U10000 $MYTMP/sftp.log $MYTMP/ftp.log |\
        grep -v -e "^--- " -e "^+++ " -e "^@@ " |\
        sed -e "s/^+/FTP /" -e "s/^ /SFTP /" -e "s/^-/SFTP /" |\
        sed -e "s/ UNKNOWN / /" -e "s/ +.*/]/" |
        awk -F' ' '{print $3, $2, $1}' |\
        sort | uniq
    
    # diff -U10000 $MYTMP/sftp.log $MYTMP/ftp.log |\
    #       -> the U1000 gives a lot of context, so all transfers should be
    #          here, assuming at least a few of each type of transfer.  May
    #          have to examine process as we move people off FTP.
    #    grep -v -e "^--- " -e "^+++ " -e "^@@ " |\
    #       -> Eliminate the headers of the diff.  Might be better to just
    #          lop off the first few lines of the output, and look for @@s later
    #          to indicate a problem.
    #    sed -e "s/^+/FTP /" -e "s/^ /SFTP /" -e "s/^-/SFTP /" |\
    #       -> explained above.  Look for the first char in the line of the diff
    #    sed -e "s/ UNKNOWN / /" -e "s/ +.*/]/" |
    #       -> Remove excess data
    #    awk -F' ' '{print $3, $2, $1}' |\
    #       -> reorder output
    #    sort | uniq
    #       -> remove redundancies.
    

    Commonly forgotten tools

    "Here documents"

    Much less picky about formatting characters than many other ways to store data.
    ## Classic:
    cat <<__ENDOFTEXT
    this text is to be output.
    __ENDOFTEXT
    
  • really messes with indentation
    # Starting your "here doc" with "<<-" will 
    # strip off leading tab characters. 
        cat <<-__ENDOFTEXT
    	this is text to be output.
    	__ENDOFTEXT
    
    
  • Running as root (or anyone)

    # Check to see who we are running as, if not
    #   root, re-invoke with sudo.
    if [[ $(whoami) != "root ]]; then
        if ! sudo -n -u root $0 $* 2>/dev/null; then
            echo "Sorry, sudo isn't configured properly
            logger "$0 sudo is broke"
            sleep 2
        fi # sudo failed
        exit 
    fi # not yet root
    # We are now running as root
    
  • Hopefully, $0 returns 0.
  • Remote administration with SSH

    Optimizing admin with SSH

    Auto-change control

    ksh "select"

    $ PS3="Pick a motorcycle ->"          
    $ select MOTORCYCLE in K100 K1200LT Buell Harley; do
    > echo "Riding the $MOTORCYCLE today"
    > done
    1) K100
    2) K1200LT
    3) Buell
    4) Harley
    Pick a motorcycle ->2
    Riding the K1200LT today
    Pick a motorcycle ->^C
    
    In modern bash now

    Prompt maddness

  • Traditional Unix Prompt: "#" or "$" (or "%")
  • More info:
    • /usr/src $
    • nick@fluffy3 /usr/src/sys/arch/amd64/compile/GENERIC.MP $
  • Crazy info
    • date time PWD user, machine+domain name, shell name and version, history number ...
  • Prompt maddness

    Shell Scripting Tips

  • Discussion? `
  • /