Chapter 8. Operations and Related Topics

8.1. Operators

assignment

variable assignment

Initializing or changing the value of a variable

=

All-purpose assignment operator, which works for both arithmetic and string assignments.

   1 var=27
   2 category=minerals  # No spaces allowed after the "=".

Caution

Do not confuse the "=" assignment operator with the = test operator.

   1 #   =  as a test operator
   2 
   3 if [ "$string1" = "$string2" ]
   4 then
   5    command
   6 fi
   7 
   8 #  if [ "X$string1" = "X$string2" ] is safer,
   9 #+ to prevent an error message should one of the variables be empty.
  10 #  (The prepended "X" characters cancel out.)

arithmetic operators

+

plus

-

minus

*

multiplication

/

division

**

exponentiation

   1 # Bash, version 2.02, introduced the "**" exponentiation operator.
   2 
   3 let "z=5**3"    # 5 * 5 * 5
   4 echo "z = $z"   # z = 125

%

modulo, or mod (returns the remainder of an integer division operation)

 bash$ expr 5 % 3
 2
 	      
5/3 = 1, with remainder 2

This operator finds use in, among other things, generating numbers within a specific range (see Example 9-11 and Example 9-15) and formatting program output (see Example 27-16 and Example A-6). It can even be used to generate prime numbers, (see Example A-15). Modulo turns up surprisingly often in numerical recipes.


Example 8-1. Greatest common divisor

   1 #!/bin/bash
   2 # gcd.sh: greatest common divisor
   3 #         Uses Euclid's algorithm
   4 
   5 #  The "greatest common divisor" (gcd) of two integers
   6 #+ is the largest integer that will divide both, leaving no remainder.
   7 
   8 #  Euclid's algorithm uses successive division.
   9 #    In each pass,
  10 #+      dividend <---  divisor
  11 #+      divisor  <---  remainder
  12 #+   until remainder = 0.
  13 #    The gcd = dividend, on the final pass.
  14 #
  15 #  For an excellent discussion of Euclid's algorithm, see
  16 #+ Jim Loy's site, http://www.jimloy.com/number/euclids.htm.
  17 
  18 
  19 # ------------------------------------------------------
  20 # Argument check
  21 ARGS=2
  22 E_BADARGS=85
  23 
  24 if [ $# -ne "$ARGS" ]
  25 then
  26   echo "Usage: `basename $0` first-number second-number"
  27   exit $E_BADARGS
  28 fi
  29 # ------------------------------------------------------
  30 
  31 
  32 gcd ()
  33 {
  34 
  35   dividend=$1             #  Arbitrary assignment.
  36   divisor=$2              #! It doesn't matter which of the two is larger.
  37                           #  Why not?
  38 
  39   remainder=1             #  If an uninitialized variable is used inside
  40                           #+ test brackets, an error message results.
  41 
  42   until [ "$remainder" -eq 0 ]
  43   do    #  ^^^^^^^^^^  Must be previously initialized!
  44     let "remainder = $dividend % $divisor"
  45     dividend=$divisor     # Now repeat with 2 smallest numbers.
  46     divisor=$remainder
  47   done                    # Euclid's algorithm
  48 
  49 }                         # Last $dividend is the gcd.
  50 
  51 
  52 gcd $1 $2
  53 
  54 echo; echo "GCD of $1 and $2 = $dividend"; echo
  55 
  56 
  57 # Exercises :
  58 # ---------
  59 # 1) Check command-line arguments to make sure they are integers,
  60 #+   and exit the script with an appropriate error message if not.
  61 # 2) Rewrite the gcd () function to use local variables.
  62 
  63 exit 0

+=

plus-equal (increment variable by a constant)nbsp;COMMAND.
     

As a simple example, consider this alternative to the echo-grep construction.

   1 # Instead of:
   2 if echo "$VAR" | grep -q txt   # if [[ $VAR = *txt* ]]
   3 # etc.
   4 
   5 # Try:
   6 if grep -q "txt" <<< "$VAR"
   7 then   #         ^^^
   8    echo "$VAR contains the substring sequence \"txt\""
   9 fi
  10 # Thank you, Sebastian Kaminski, for the suggestion.

Or, in combination with read:

   1 String="This is a string of words."
   2 
   3 read -r -a Words <<< "$String"
   4 #  The -a option to "read"
   5 #+ assigns the resulting values to successive members of an array.
   6 
   7 echo "First word in String is:    ${Words[0]}"   # This
   8 echo "Second word in String is:   ${Words[1]}"   # is
   9 echo "Third word in String is:    ${Words[2]}"   # a
  10 echo "Fourth word in String is:   ${Words[3]}"   # string
  11 echo "Fifth word in String is:    ${Words[4]}"   # of
  12 echo "Sixth word in String is:    ${Words[5]}"   # words.
  13 echo "Seventh word in String is:  ${Words[6]}"   # (null)
  14                                                  # Past end of $String.
  15 
  16 # Thank you, Francisco Lobo, for the suggestion.


Example 19-13. Prepending a line to a file

   1 #!/bin/bash
   2 # prepend.sh: Add text at beginning of file.
   3 #
   4 #  Example contributed by Kenny Stauffer,
   5 #+ and slightly modified by document author.
   6 
   7 
   8 E_NOSUCHFILE=85
   9 
  10 read -p "File: " file   # -p arg to 'read' displays prompt.
  11 if [ ! -e "$file" ]
  12 then   # Bail out if no such file.
  13   echo "File $file not found."
  14   exit $E_NOSUCHFILE
  15 fi
  16 
  17 read -p "Title: " title
  18 cat - $file <<<$title > $file.new
  19 
  20 echo "Modified file is $file.new"
  21 
  22 exit  # Ends script execution.
  23 
  24   from 'man bash':
  25   Here Strings
  26   	A variant of here documents, the format is:
  27   
  28   		<<<word
  29   
  30   	The word is expanded and supplied to the command on its standard input.
  31 
  32 
  33   Of course, the following also works:
  34    sed -e '1i\
  35    Title: ' $file


Example 19-14. Parsing a mailbox

   1 #!/bin/bash
   2 #  Script by Francisco Lobo,
   3 #+ and slightly modified and commented by ABS Guide author.
   4 #  Used in ABS Guide with permission. (Thank you!)
   5 
   6 # This script will not run under Bash versions < 3.0.
   7 
   8 
   9 E_MISSING_ARG=67
  10 if [ -z "$1" ]
  11 then
  12   echo "Usage: $0 mailbox-file"
  13   exit $E_MISSING_ARG
  14 fi
  15 
  16 mbox_grep()  # Parse mailbox file.
  17 {
  18     declare -i body=0 match=0
  19     declare -a date sender
  20     declare mail header value
  21 
  22 
  23     while IFS= read -r mail
  24 #         ^^^^                 Reset $IFS.
  25 #  Otherwise "read" will strip leading & trailing space from its input.
  26 
  27    do
  28        if [[ $mail =~ "^From " ]]   # Match "From" field in message.
  29        then
  30           (( body  = 0 ))           # "Zero out" variables.
  31           (( match = 0 ))
  32           unset date
  33 
  34        elif (( body ))
  35        then
  36             (( match ))
  37             # echo "$mail"
  38             # Uncomment above line if you want entire body of message to display.
  39 
  40        elif [[ $mail ]]; then
  41           IFS=: read -r header value <<< "$mail"
  42           #                          ^^^  "here string"
  43 
  44           case "$header" in
  45           [Ff][Rr][Oo][Mm] ) [[ $value =~ "$2" ]] && (( match++ )) ;;
  46           # Match "From" line.
  47           [Dd][Aa][Tt][Ee] ) read -r -a date <<< "$value" ;;
  48           #                                  ^^^
  49           # Match "Date" line.
  50           [Rr][Ee][Cc][Ee][Ii][Vv][Ee][Dd] ) read -r -a sender <<< "$value" ;;
  51           #                                                    ^^^
  52           # Match IP Address (may be spoofed).
  53           esac
  54 
  55        else
  56           (( body++ ))
  57           (( match  )) &&
  58           echo "MESSAGE ${date:+of: ${date[*]} }"
  59        #    Entire $date array             ^
  60           echo "IP address of sender: ${sender[1]}"
  61        #    Second field of "Received" line    ^
  62 
  63        fi
  64 
  65 
  66     done < "$1" # Redirect stdout of file into loop.
  67 }
  68 
  69 
  70 mbox_grep "$1"  # Send mailbox file to function.
  71 
  72 exit $?
  73 
  74 # Exercises:
  75 # ---------
  76 # 1) Break the single function, above, into multiple functions,
  77 #+   for the sake of readability.
  78 # 2) Add additional parsing to the script, checking for various keywords.
  79 
  80 
  81 
  82 $ mailbox_grep.sh scam_mail
  83   MESSAGE of Thu, 5 Jan 2006 08:00:56 -0500 (EST) 
  84   IP address of sender: 196.3.62.4

Exercise: Find other uses for here strings, such as, for example, feeding input to dc.

./usr/share/doc/abs-guide/html/operations.html0000644000000000000000000005331111346257151020256 0ustar rootroot Operations and Related Topics

Chapter 8. Operations and Related Topics

8.1. Operators

assignment

variable assignment

Initializing or changing the value of a variable

=

All-purpose assignment operator, which works for both arithmetic and string assignments.

   1 var=27
   2 category=minerals  # No spaces allowed after the "=".

Caution

Do not confuse the "=" assignment operator with the = test operator.

   1 #   =  as a test operator
   2 
   3 if [ "$string1" = "$string2" ]
   4 then
   5    command
   6 fi
   7 
   8 #  if [ "X$string1" = "X$string2" ] is safer,
   9 #+ to prevent an error message should one of the variables be empty.
  10 #  (The prepended "X" characters cancel out.)

arithmetic operators

+

plus

-

minus

*

multiplication

/

division

**

exponentiation

   1 # Bash, version 2.02, introduced the "**" exponentiation operator.
   2 
   3 let "z=5**3"    # 5 * 5 * 5
   4 echo "z = $z"   # z = 125

%

modulo, or mod (returns the remainder of an integer division operation)

 bash$ expr 5 % 3
 2
 	      
5/3 = 1, with remainder 2

This operator finds use in, among other things, generating numbers within a specific range (see Example 9-11 and Example 9-15) and formatting program output (see Example 27-16 and Example A-6). It can even be used to generate prime numbers, (see Example A-15). Modulo turns up surprisingly often in numerical recipes.


Example 8-1. Greatest common divisor

   1 #!/bin/bash
   2 # gcd.sh: greatest common divisor
   3 #         Uses Euclid's algorithm
   4 
   5 #  The "greatest common divisor" (gcd) of two integers
   6 #+ is the largest integer that will divide both, leaving no remainder.
   7 
   8 #  Euclid's algorithm uses successive division.
   9 #    In each pass,
  10 #+      dividend <---  divisor
  11 #+      divisor  <---  remainder
  12 #+   until remainder = 0