Sams Teach Yourself Shell Programming in 24 Hours
(Publisher: Macmillan Computer Publishing)
Author(s): Sriranga Veeraraghavan
ISBN: 0672314819
Publication Date: 01/01/99

Table of Contents


Appendix C
Quiz Answers

Chapter 1

1.  The first is a simple command. The second is a compound command constructed from two simple commands. The last two are complex commands.
2.  There is no effect. The output will be the same for both commands.
3.  The two types are Bourne (sh, ksh, or bash) and C (csh, tcsh).

Chapter 2

1.  The files are /etc/profile and .profile.
2.  If PATH is not set, the shell cannot find the commands you want to execute. If MANPATH is not set, the shell cannot locate the online help.
3.  It specifies that the shell /bin/sh should be used to execute the script.
4.  The man command.

Chapter 3

1.  Invisible files are files whose names start with the . character. You can list them by specifying the -a option to ls.
2.  No. Each of these commands will produce the same results.
3.  On Solaris and HPUX use the command
   $ wc -lm

On Linux use the command
   $ wc -lc
4.  (b) and (c) will generate error messages indicating that homework is a directory.

Chapter 4

1.  (a) and (d) are absolute pathnames. (b) and (c) are relative pathnames.
2.  The pwd command will output the full path to your home directory. In my case the path is
   /home/ranga
3.  The following command will work:
   cp -r /usr/local /opt/pgms
4.  The following commands will work:
   cp -r /usr/local /opt/pgms ; rm -r /usr/local
5.  No, you cannot use the rmdir command, because the directory is not empty. You can use the following command:
   $ rm -r backup

Chapter 5

1.  The file types of these files are
/dev/rdsk/c0t1d0 character special file
/etc/passwd regular file
/usr/local directory
/usr/sbin/ping regular file
2.  The owner and groups of these files are
/dev/rdsk/c0t1d0 owner bin group sys
/etc/passwd owner root group sys
/usr/local owner bin group bin
/usr/sbin/ping owner root group bin
3.  The permissions of these files are
/dev/rdsk/c0t1d0 owner read and write
group read
other none
/etc/passwd owner read
group read
other read
/usr/local owner read, write, and execute
group read, write, and execute
other read, write, and execute
/usr/sbin/ping owner read and SUID execute
group read and execute
other read and execute

Chapter 6

1.  By putting an ampersand at the end of the command line.
2.  With the ps command.
3.  Use the suspend key (usually Ctrl-Z) to stop the foreground process and then use the bg command to resume it in the background.

Chapter 7

1.  (a) and (d) are valid variable names. (b) starts with a number thus it is invalid. (c) contains the & character, which is not a valid character for variable names.
2.  These assignments are valid in ksh and bash, but not in sh. The shell, sh, only supports scalar variables.
3.  To access the array item at index 5 use the following:
   ${adams[5]}

To access every item in the array use the following:
   ${adams[@]}
4.  An environment variable’s value can be accessed by child processes of a shell. A local variable is restricted to a particular shell; it cannot be used by child processes of a shell.

Chapter 8

1.  The following command will accomplish this task:
   $ ls *hw[0-9][0-9][2-6].???
2.  If MYPATH is unset, it is set to the given value, which is then substituted.
3.  If MYPATH is unset, the given value is substituted for it. MYPATH remains unset.
4.  10

Chapter 9

1.  Double quotes accomplish this easily but not single quotes:
   $ echo "It's <party> time!"
2.  The following command will accomplish this task:
   $ echo "$USER owes     \$$DEBT"

Chapter 10

1.  The difference is that the first command will try to run the command without checking if it is executable. Thus if the file exists but is not executable, the command will fail. The second command takes this into account and attempts to run the command only if it is executable.
2.  The output is “Your binaries are stored in your home directory.”
3.  Any of the following commands are valid:
   $ test -d /usr/bin || test -h /usr/bin
   $ [ -d /usr/bin ] || [ -h /usr/bin ]
   $ test -d /usr/bin -o -h /usr/bin
   $ [ -d /usr/bin  -o -h /usr/bin ]
4.  The following case statement covers the given combinations and several more:
   case "$ANS" in
       [Yy]|[Yy][Ee][Ss]) ANS="y" ;;
       *) ANS="n" ;;
   esac

Chapter 11

1.  Here is one possible implementation:
   x=0
   while [ $x -lt 10 ]
   do
       x=$(($x+1))
       y=0
       while [ $y -lt $x ] ; do
    echo "$y \c"
          y=$(($y+1))
       done
       echo
   done
2.  Here is one possible implementation:
   #!/bin/bash
   select FILE in * "Exit Program"
   do

      if [ -z "$FILE" ] ; then continue ; fi

      if [ "$FILE" = "Exit Program" ] ; then break ; fi

      if [ ! -f "$FILE" ] ; then
         echo "$FILE is not a regular file."
         continue
      fi

    echo $FILE
    cat $FILE
done

Chapter 12

1.  One correct implementation is as follows:
   #!/bin/sh

   USAGE="Usage: `basename $0` [-c|-t] [files|directories]"

   if [ $# -lt 2 ] ; then
      echo "$USAGE" ;
      exit 1 ;
   fi

   case "$1" in
      -t|-x) TARGS=${1}vf ; shift
          for i in "$@" ; do
              if [ -f "$i" ] ; then
                 FILES='tar $TARGS "$i" 2>/dev/null'
                 if [ $? -eq 0 ] ; then
                    echo ; echo "$i" ; echo "$FILES"
                 else
                    echo "ERROR: $i not a tar file."
                 fi
              else
                 echo "ERROR: $i not a file."
              fi
          done
          ;;
      -c) shift ; TARGS="-cvf" ;
          tar $TARGS archive.tar "$@"
          ;;
      *) echo "$USAGE"
         exit 0
         ;;
   esac
   exit $?
2.  One possible implementation is as follows:
   #!/bin/sh

   USAGE="Usage: 'basename $0' [-v] [-x] [-f] [filename] [-o]
   [filename]";

   VERBOSE=false
   EXTRACT=false

   while getopts f:o:x:v OPTION ; do
     case "$OPTION" in
        f) INFILE="$OPTARG" ;;
        o) OUTFILE="$OPTARG" ;;
        v) VERBOSE=true ;;
        x) EXTRACT=true ;;
       \?) echo "$USAGE" ;
           exit 1
           ;;
     esac
   done

   shift `echo "$OPTIND - 1" | bc`

   if [ -z "$1" -a -z "$INFILE" ] ; then
     echo "ERROR: Input file was not specified."
     exit 1
   fi
   if [ -z "$INFILE" ] ; then INFILE="$1" ; fi

   : ${OUTFILE:=${INFILE}.uu}

   if [ -f "$INFILE" ] ; then
     if [ "$EXTRACT" = "true" ] ; then
        if [ "$VERBOSE" = "true" ] ; then
            echo "uudecoding $INFILE... \c"
        fi

        uudecode "$INFILE" ; RET=$?
     else
        if [ "$VERBOSE" = "true" ] ; then
            echo "uuencoding $INFILE to $OUTFILE... \c"
        fi
        uuencode "$INFILE" "$INFILE" > "$OUTFILE" ; RET=$?
     fi

     if [ "$VERBOSE" = "true" ] ; then
        MSG="Failed" ; if [ $RET -eq 0 ] ; then MSG="Done." ; fi
        echo $MSG
     fi
   else
     echo "ERROR: $INFILE is not a file."
   fi
   exit $RET

Chapter 13

1.  The simplest possible answer is as follows:
   #!/bin/sh

   if [ $# -lt 2 ] ; then
      echo "ERROR: Insufficient arguments." ;
      exit 1 ;
   fi
   case "$1" in
      -o) printf "%o\n" "$2" ;;
      -x) printf "%x\n" "$2" ;;
      -e) printf "%e\n" "$2" ;;
      *) echo "ERROR: Unknown conversion, $1!" ;;
   esac
2.  The rewritten script is as follows:
   #!/bin/sh
   if [ $# -lt 2 ] ; then
      echo "ERROR: Insufficient arguments." >&2
      exit 1 ;
   fi

   case "$1" in
      -o) printf "%o\n" "$2" ;;
      -x) printf "%x\n" "$2" ;;
      -e) printf "%e\n" "$2" ;;
      *) echo "ERROR: Unknown conversion, $1!" >&2 ;;
   esac

Chapter 14

1.  A possible implementation is
   mymkdir() {

     if [ $# -lt 1 ] ; then
        echo "ERROR: Insufficient arguments." >&2
        return 1
     fi

     mkdir -p "$1" > /dev/null 2>&1
     if [ $? -eq 0 ] ; then
        cd "$1" > /dev/null 2>&1
        if [ $? -eq 0 ] ; then
            pwd ;
        else
            echo "ERROR: Could not cd to $1." >&2
        fi
     else
        echo "ERROR: Could not mkdir $1." >&2
     fi
   }
2.  A possible implementation is
   Prompt_RESPONSE() {

     if [ $# -lt 1 ] ; then
        echo "ERROR: Insufficient arguments." >&2
        return 1
     fi

     RESPONSE=
     while [ -z "$RESPONSE" ]
     do
        echo "$1 \c "
        read RESPONSE
     done

     export RESPONSE
   }

Chapter 15

1.  A sample implementation is
   lspids() {

     USAGE="Usage: lspids [-h] process"
     HEADER=false
     PSCMD="/bin/ps -ef"

     case "$1" in
        -h) HEADER=true ; shift ;;
     esac

     if [ -z "$1" ] ; then
        echo $USAGE ;
        return 1 ;
     fi


     if [ "$HEADER" = "true" ] ; then
        $PSCMD 2> /dev/null | head -n 1 ;
     fi

     $PSCMD 2> /dev/null | grep "$1"| grep -v grep
   }

For Linux or FreeBSD, change the variable PSCMD from
   PSCMD="/bin/ps -ef"

to
   PSCMD="/bin/ps -auwx"
2.  The following is one possible implementation:
   lspids ()
   {
     USAGE="Usage: lspids [-h|-s] process";
     HEADER=false;
     SORT=false;
     PSCMD="/bin/ps -ef";
     SORTCMD="sort -rn -k 2,2";
     for OPT in $@;
     do
        case "$OPT" in
            -h)
                HEADER=true;
                shift
            ;;
            -s)
                SORT=true;
                shift
            ;;
            -*)
                echo $USAGE;
                return 1
            ;;
        esac;
    done;
    if [ -z "$1" ]; then
        echo $USAGE;
        return 1;
    fi;
    if [ "$HEADER" = "true" ]; then
        $PSCMD | head -1;
    fi;
    if [ "$SORT" = "true" ]; then
        $PSCMD 2> /dev/null | grep "$1" | grep -v grep |    $SORTCMD;
    else
        $PSCMD 2> /dev/null | grep "$1" | grep -v grep;
    fi
   }

For LINUX and FreeBSD, change the variable SORTCMD to
   SORTCMD="sort -rn"

instead of
   SORTCMD="sort -rn -k 2,2"

You will also need to change the variable PSCMD from
   PSCMD="/bin/ps -ef"

to
   PSCMD="/bin/ps -auwx"

Chapter 16

1.  One possible implementation is
   sgrep() {
     if [ $# -lt 2 ] ; then
        echo "USAGE: sgrep pattern files" >&2
        exit 1
     fi

     PAT="$1" ; shift ;
     for i in $@ ;
     do
        if [ -f "$i" ] ; then
            sed -n "/$PAT/p" $i
        else
            echo "ERROR: $i not a file." >&2
        fi
     done

     return 0
   }
2.  The following command does the job:
   $ uptime | sed 's/.* load/load/'
3.  There are two possible solutions:
   $ df -k | sed -n '/^\//p'
   $ df -k | sed '/^[^\/]/d'
4.  The following command will solve this problem:
   /bin/ls -al | sed -e '/^[^\-]/d' -e 's/ *[0-9].* / /'

Chapter 17

1.  A possible implementation is as follows:
   #!/bin/sh

   if [ $# -lt 1 ] ; then
     echo "USAGE: `basename $0` files"
     exit 1
   fi

   awk '{
     for (i=NF;i>=1;i—) {
        printf("%s ",$i) ;
     }
     printf("\n") ;
   }' $@
2.  A possible solution is
   #!/bin/sh
   awk 'BEGIN { FS=":" ; }
     $1 == "B" {
        BAL=$NF ; next ;
     }
     $1 == "D" {
        BAL += $NF ;
     }
     ($1 == "C") || ($1 == "W") {
        BAL-=$NF ;
     }
     ($1 == "C") || ($1 == "W") || ($1 == "D") {
        printf "%10-s %8.2f\n",$2,BAL ;
     }
   ' account.txt ;

Alternatively, you can use the -F option:
   #!/bin/sh
   awk -F: '
     $1 == "B" {
        BAL=$NF ; next ;
     }
     $1 == "D" {
        BAL += $NF ;
     }
     ($1 == "C") || ($1 == "W") {
        BAL-=$NF ;
     }
     ($1 == "C") || ($1 == "W") || ($1 == "D") {
        printf "%10-s %8.2f\n",$2,BAL ;
     }
   ' account.txt ;
3.  The following is a possible implementation:
   #!/bin/sh
   awk -F: '
     $1 == "B" {
        BAL=$NF ;
        next ;
     }
     $1 == "D" {
        BAL += $NF ;
     }
     ($1 == "C") || ($1 == "W") {
        BAL-=$NF ;
     }
     ($1 == "C") || ($1 == "W") || ($1 == "D") {
        printf "%10-s %8.2f\n",$2,BAL ;
     }
     END {
        printf "-\n%10-s %8.2f\n","Total",BAL ;
     }
   ' account.txt ;
4.  A possible implementation is
   #!/bin/sh
   awk -F: '
     $1 == "B" {
        BAL=$NF ;
        next ;
     }
     $1 == "M" {
        MIN=$NF ;
        next ;
     }
     $1 == "D" {
        BAL += $NF ;
     }
     ($1 == "C") || ($1 == "W") {
        BAL-=$NF ;
     }
     ($1 == "C") || ($1 == "W") || ($1 == "D") {
        printf "%10-s %8.2f",$2,BAL ;
        if ( BAL < MIN ) { printf " * Below Min. Balance"    }
        printf "\n" ;
     }
     END {
        printf "-\n%10-s %8.2f\n","Total",BAL ;
     }
   ' account.txt ;

Chapter 18

1.  The following command will accomplish this task:
   $ type process2
2.  The following command will accomplish this task:
   $ find /data -name '*process2*' -print
3.  The following command will accomplish this task:
   PRICE=`echo "scale=2; 3.5 \* $PRICE" | bc`

Chapter 19

1.  Here is a possible implementation:
   trap CleanUp 2 15
   trap Init 1
   trap "quit=true" 3
   PROG="$1"
   Init

   while : ;
   do
     wait $!
     if [ "$quit" = true ] ; then exit 0 ; fi
     $PROG &
   done
2.  Here is a possible implementation:
   #! /bin/sh

   AlarmHandler() {
     echo "Got SIGALARM, cmd took too long."
     KillSubProcs
     exit 14
   }

   IntHandler() {
     echo "Got SIGINT, user interrupt."
     KillSubProcs
     exit 2
   }

   KillSubProcs() {
     kill ${CHPROCIDS:-$!}
     if [ $? -eq 0 ] ; then echo "Sub-processes killed." ; fi
   }

   SetTimer() {
     DEF_TOUT=${1:-10};
     if [ $DEF_TOUT -ne 0 ] ; then
        sleep $DEF_TOUT && kill -s 14 $$ &
        CHPROCIDS="$CHPROCIDS $!"
        TIMERPROC=$!
     fi
   }

   UnsetTimer() {
     kill $TIMERPROC
   }

   # main()

   trap AlarmHandler 14
   trap IntHandler 2

   SetTimer 15
   $PROG &
   CHPROCIDS="$CHPROCIDS $!"
   wait $!
   UnsetTimer
   echo "All Done."
  exit 0

Chapter 20

1.  The three main methods are
  Issue the script in the following fashion:
   $ /bin/sh option script arg1 arg2 arg3
  Change the first line of the script to
   #!/bin/sh option
  Use the set command as follows:
   set option

Here option is the debugging option you want to enable.
2.  Here is one possible implementation:
   Debug() {
     if [ "$DEBUG" = "true" ] ; then
        if [ "$1" = "on"  -o "$1" = "ON" ] ; then
            set -x
        else
            set +x
            echo " >Press Enter To Continue< \c"
            read press_enter_to_continue
        fi
     fi
   }

Chapter 21

1.  One possible implementation is
   ################################################
   # Name: toLower
   # Desc: changes an input string to lower case
   # Args: $@ -> string to change
   ################################################

   toLower() {
     echo $@ | tr '[A-Z]' '[a-z]' ;
   }
2.  One possible implementation is
   ################################################
   # Name: toUpper
   # Desc: changes an input string to upper case
   # Args: $@ -> string to change
   ################################################

   toUpper() {
     echo $@ | tr '[a-z]' '[A-Z]'
   }
3.  One possible solution is
   ################################################
   # Name: isSpaceAvailable
   # Desc: returns true (0) if space available
   # Args: $1 -> The directory to check
   #       $2 -> The amount of space to check for
   ################################################

   isSpaceAvailable() {

     if [ $# -lt 2 ] ; then
        printERROR "Insufficient Arguments."
        return 1
     fi

     if [ ! -d "$1" ] ; then
        printERROR "$1 is not a directory."
        return 1
     fi

     if [ `getSpaceFree "$1"` -gt "$2" ] ; then
        return 0
     fi

     return 1
   }
4.  One possible solution is
   ################################################
   # Name: isSpaceAvailable
   # Desc: returns true (0) if space available
   # Args: $1 -> The directory to check
   #       $2 -> The amount of space to check for
   #       $3 -> The units for $2 (optional)
   #                 k for kilobytes
   #                 m for megabytes
   #                 g for gigabytes
   ################################################

   isSpaceAvailable() {
     if [ $# -lt 2 ] ; then
        printERROR "Insufficient Arguments."
        return 1
     fi

     if [ ! -d "$1" ] ; then
        printERROR "$1 is not a directory."
        return 1
     fi

     SPACE_MIN="$2"

     case "$3" in
        [mM]|[mM][bB])
            SPACE_MIN=`echo "$SPACE_MIN * 1024" | bc` ;;
        Γ|Γ[bB])
            SPACE_MIN=`echo "$SPACE_MIN * 1024 * 1024" | bc` ;;
     esac

     if [ `getSpaceFree "$1"`1 -gt "$SPACE_MIN" ] ; then
        return 0
     fi

     return 1
   }
5.  One possible solution is
   ################################################
   # Name: isUserRoot
   # Desc: returns true (0) if the users UID=0
   # Args: $1 -> a user name (optional)
   ################################################

   isUserRoot() {
     if [ "`getUID $1`" -eq 0 ] ; then
        return 0
     fi
     return 1
   }

Chapter 22

1.  One possible simplification is
   # initalize the destination directory

   DESTDIR="$2";

   # check if the destination exits

   if [ ! -d "$DESTDIR" ] ; then

     # if the destination doesn't exist then assume the destination is
     # the new name for the directory

     DESTDIR="`/usr/bin/dirname $2`"
     NEWNAME="`'/bin/basename $2`"

   fi
   # if dirname returns a relative dir we will be confused after cd'ing
   # latter on. So reset it to the full path.

   DESTDIR=`(cd $DESTDIR ; pwd ; )`

   # if the parent of the destination doesn't exist,
   # were in trouble. Tell the user and exit.

   if [ ! -d "$DESTDIR" ] ; then
     printERROR "A parent of the destination directory $2 does not    exist"
   fi
2.  Use grep -i instead of grep.
3.  They can be rewritten as functions and stored in a shell library that both scripts can access.
4.  We can change the lines
    55  grep "$1" "$TMPF1" > "$TMPF2" 2> /dev/null
    56  Failed $? "No matches found."

to
    55  sed -n "/^$1[^:]*:/p" "$TMPF1" > "$TMPF2" 2> /dev/null
    56  test -s "$TMPF2" > /dev/null
    57  Failed $? "No matches found."

We can also change the line
   79          grep -v "$LINE" "$TMPF1" > "$TMPF1.new" 2> /dev/null

to
   sed -e "s/^$LINE$//" "$TMPF1" > "$TMPF1.new" 2> /dev/null
5.  Add a signal handler. A simple one might be
   trap 'echo "Cleaning Up." ; doCleanUp ; exit 2; ' 2 3 15

You should add this to the script before the line:
   cp "$MYADDRESSBOOK" "$TMPF1" 2> /dev/null

Chapter 23

1.  A possible implementation is
   getCharCount() {
     case 'getOSName' in
        bsd|sunos|linux)
            WCOPT="-c" ;;
        *)
            WCOPT="-m" ;;
     esac

     wc $WCOPT $@
   }

Appendix A

1.  The “I/O” section; use >> to append.
2.  In the section “Reserved Words and Built-in Shell Commands,” find the case statement, which shows the word esac must come at the end.
3.  In the section “Reserved Words and Built-in Shell Commands,” find the jobs command. Note that (Korn/Bash) is indicated. This command is available in the Korn shell and Bash shell but not the Bourne shell.
4.  In the section “Regular Expression Wildcards,” note the + sign is not listed under “Limited Regular Expression Wildcards,” which are always supported. It is in the next section, “Extended Regular Expression Wildcards,” which are supported only on some commands. Check the man pages to see if a particular command supports this wildcard.
5.  In the section “Parameters and Variables,” subsection “Built-in Shell Variables,” $? is what you are looking for.


Table of Contents