Thursday, July 28, 2011

Operators



In ksh, the default shell used in AIX, as well as other shells used in UNIX and Linux, it's important to know how to use test, logical, and substitution operators.

Test operators


When writing shell scripts, test operators are crucial to error checking and for checking the status of files. The following test operators are just a few that you can use in ksh as well as other standard UNIX shells:
  • -d <file>:<file> is a directory
  • -e <flle>:<file> exists
  • -f <file>:<file> is a regular file
  • -n <string>:<string> is not NULL
  • -r <file>: The user has Read permissions to <file>
  • -s <file>:<file> size is greater than 0
  • -w <file>: The user has Write permissions to <file>
  • -x <file>: The user has Execute permissions to <file>
  • -z <string>:<string> is null
  • -L <file>:<file> is a symbolic link
Remember, in UNIX directories, devices, symbolic links, and other objects are all files, so the test operators shown above will work with every type of file.
Everyone has an individual style of shell scripting. Whether they use [[ ]] or [ ] in test statements, the above test operators will function the same. This article uses [[ ]]. Listing 11 shows how you can use a few of the test operators listed above.


Listing 11. Using test operators
#!/usr/bin/ksh

while true
do
  echo "\nEnter file to check:  \c"
  read _FNAME

  if [[ ! -e "${_FNAME}" ]]
  then
    echo "Unable to find file '${_FNAME}'"
    continue
  fi

  if [[ -f "${_FNAME}" ]]
  then
    echo "${_FNAME} is a file."
  elif [[ -d "${_FNAME}" ]]
  then
    echo "${_FNAME} is a directory."
  elif [[ -L "${_FNAME}" ]]
  then
    echo "${_FNAME} is a symbolic link."
  else
    echo "Unable to determine file type for '${_FNAME}'"
  fi

  [[ -r "${_FNAME}" ]] && echo "User ${USER} can read '${_FNAME}'"
  [[ -w "${_FNAME}" ]] && echo "User ${USER} can write to '${_FNAME}'"
  [[ -x "${_FNAME}" ]] && echo "User ${USER} can execute '${_FNAME}'"

  if [[ -s "${_FNAME}" ]]
  then
    echo "${_FNAME} is NOT empty."
  else
    echo "${_FNAME} is empty."
  fi
done


Executing the code in Listing 11 and checking a few file names produces the output shown in Listing 12.

Listing 12. Output from executing the test operators
# ls –l
-rwxr-xr-x    1 cormany  atc             786 Feb 22 16:11 check_file
-rw-r--r--    1 cormany  atc               0 Aug 04 20:57 emptyfile

# ./check_file

Enter file to check:  badfilename
Unable to find file 'badfilename'

Enter file to check:  check_file
check_file is a file.
User cormany can read 'check_file'
User cormany can write to 'check_file'
User cormany can execute 'check_file'
check_file is NOT empty.

Enter file to check:  emptyfile
emptyfile is a file.
User cormany can read 'emptyfile'
User cormany can write to 'emptyfile'
emptyfile is empty.


To learn more about test operators and to see a complete listing of test operators, execute man test.
Logical operators
Another important set of operators in UNIX is the logical operators. Like in most modern programming languages, the AND andOR statements are necessary for definitive conditional evaluations of expressions or their values.
you'll notice that I favor logical operators over writing several lines of code. This keeps the scripts clean and easy to manage. One of the first things I do when writing a script is to write theexit_msg() function:
exit_msg() {
  [[ $# -gt 1 ]] && echo "${0##*/} (${1}) – ${2}"
  exit ${1:-0}
}


rather than having ugly and bloated code like that shown in Listing 13.

Listing 13. The alternative to using the exit_msg() function and clean logical operators 
#!/usr/bin/ksh

if [[ -n ${_NUM1} ]]
then
  unset _NUM1
fi

if [[ -n ${_NUM2} ]]
then
  unset _NUM2
fi

while [[ -z ${_NUM1} ]] || [[ -z ${_NUM2} ]]
do
  echo "Enter 2 sets of numbers:  \c"
  read _NUM1 _NUM2
done

echo "Enter file to log results to: \c"
read _FNAME

if [[ ! -e "${_FNAME}" ]]
then
  echo "File '${_FNAME}' doesn't exist. A new log will be created."
fi

touch "${_FNAME}"

if [[ ! -w "${_FNAME}" ]]
then
  echo "Unable to write to file '${_FNAME}'"
  exit 1
fi

expr ${_NUM1} \/ 1 > /dev/null 2>&1
if [[ $? -ne 0 ]]
then
  echo "Number '${_NUM1}' is not numeric."
  exit 2
fi

expr ${_NUM2} \/ 1 > /dev/null 2>&1
if [[ $? -ne 0 ]]
then
  echo "Number '${_NUM2}' is not numeric."
  exit 2
fi

echo "${_NUM1},${_NUM2}" >> "${_FNAME}"

By using a simple function like exit_msg() and a few logical operators, the script could be condensed into the better-looking and easier-to-understand program shown in Listing 14.

Listing 14. Cleaner version of a script using functions and logical operators 
#!/usr/bin/ksh

exit_msg() {
  [[ $# -gt 1 ]] && echo "${0##*/} (${1}) - ${2}"
  exit ${1:-0}
}

[[ -n ${_NUM1} ]] && unset _NUM1
[[ -n ${_NUM2} ]] && unset _NUM2

while [[ -z ${_NUM1} ]] || [[ -z ${_NUM2} ]]
do
  echo "Enter 2 sets of numbers:  \c"
  read _NUM1 _NUM2
done

echo "Enter file to log results to: \c"
read _FNAME

[[ ! -e "${_FNAME}" ]] && echo 
   "File '${_FNAME}' doesn't exist. A new log will be created."

touch "${_FNAME}"

[[ ! -w "${_FNAME}" ]] && exit_msg 1 
   "Unable to write to file '${_FNAME}'"

expr ${_NUM1} \/ 1 > /dev/null 2>&1
[[ $? -ne 0 ]] && exit_msg 2 "Number '${_NUM1}' is not numeric."

expr ${_NUM2} \/ 1 > /dev/null 2>&1
[[ $? -ne 0 ]] && exit_msg 2 "Number '${_NUM2}' is not numeric."

echo "${_NUM1},${_NUM2}" >> "${_FNAME}"


The previous examples focused more on the AND (&&) and OR (||) logical operators. In addition to these, you can use the AND (–a) and OR (–o) operators as discussed in the section describing [ ] versus [[ ]]. If using the test command or single brackets ([ ]), use â€“a and â€“o to evaluate the expression. If, however, you use double brackets ([[ ]]), use && and ||:

# [[ "Paul" != "Xander" && 2 -gt 0 ]]
# echo $?
0

# [ "Paul" != "Xander" -a 2 -gt 0 ]
# echo $?
0


Comparison test operators
Another set of test operators is called comparison test operators. Like the previous set of test operators, comparison test operators are a handy way to perform error checking or to test values against another value. The previous test operators were used mostly on files or to see if a variable was defined, but the comparison test operators are used more on strings and numeric values. This can be useful when checking dates, file sizes, if one string is the same as another string, and so on.
The comparison test operators are:
  • <fileA> -nt <fileB>: fileA is newer than fileB
  • <fileA> -ot <fileB>: fileA is older than fileB
  • <fileA> -ef <fileB>: fileA and fileB point to the same file
  • <string> = <pattern>: string matches pattern
  • <string> != <pattern>: string does not match pattern
  • <stringA> < <stringB>: stringA comes before stringB in dictionary order
  • <stringA> > <stringB>: stringA comes after stringB in dictionary order
  • <exprA> -eq <exprB>: expressionA is equal to expressionB
  • <exprA> -ne <exprB>: expressionA is not equal to expressionB
  • <exprA> -lt <exprB>: expressionA is less than expressionB
  • <exprA> -gt <exprB>: expressionA is greater than expressionB
  • <exprA> -le <exprB>: expressionA is less than or equal to expressionB
  • <exprA> -ge <exprB>: expressionA is greater than or equal to expressionB
You use the same format on comparison test operators as other operators. You can use either test[ ], or [[ ]]. Listing 15,Listing 16, and Listing 17 display how you can use numeric, string, and file comparisons, respectively.

Listing 15. Numeric comparisons
# ls -l *.file
-rw-r--r--    1 cormany  atc              21 Feb 22 2006  Pauls.file
-rw-r--r--    1 cormany  atc              22 Aug 04 20:57 Xanders.file

# [[ "Pauls.file" -ot "Xanders.file" ]]
# echo $?
0


Listing 16. String comparison

# _PSIZE=`ls -l Pauls.file | awk '{print $5}'`

# _XSIZE=`ls -l Xanders.file | awk '{print $5}'`

# [[ ${_PSIZE} -lt ${_XSIZE} ]]

# echo $?
0


Listing 17. File comparison

# [[ "cat" = "dog" ]]

# echo $?
1


Substitution operators
It's easy to forget to define a variable or assign a value to it when a script grows or you haven't touched the script for years and need to add to it. Other times, it would be handy to tell users that a value is set or set up some defaults for your users. Substitution operators are a great address to these problems:
  • ${var-value}: If <var> exists, return <var>'s value. If <var> doesn't exist, return <value>.
  • ${var=value}: If <var> exists, return <var>'s value. If <var> doesn't exist, set <var> to <value> and return <value>.
  • ${var+value}: If <var> exists, return <value>. If <var> doesn't exist, return NULL.
  • ${var?value}: If <var> exists, return <var>'s value. If <var> doesn't exist, exit the command or script and display the error message set with <value>. If <value> isn't set, a default error message of "Parameter null or not set" is displayed.
  • ${var:-value}: If <var> exists and isn't NULL, return <var>'s value. If <var> doesn't exist or is NULL, return <value>.
  • ${var:=value}: If <var> exists and isn't NULL, return <var>'s value. If <var> doesn't exist or is NULL, set <var> to<value> and return <value>.
  • ${var:+value}: If <var> exists and isn't NULL, return <value>. If <var> doesn't exist or is NULL, return NULL.
  • ${var:?value}: If <var> exists and isn't NULL, return <var>'s value. If <var> doesn't exist or is NULL, exit the command or script and display the error message set with <value>. If <value> isn't set, a default error message of "Parameter null or not set" is displayed.
Note the subtle difference between the first group of four definitions and the second set of four. The last set includes a colon (:) between the variable name and the substitution operator, which adds the check to see if the variable is NULL, as well. Another important note to think about when trying to assign values to variables with substitution operators is that assigning a value to a variable has the same rules as defining a variable normally from the command line or a script. Protected reserved variables cannot be overwritten with a new value (for example, $1$2$3).
Listing 18 provides an example of how the variables work. Note that you can combine several substitution operators, as shown in the last line of the script.

Listing 18. Using substitution operators
# cat subops_examples

#!/usr/bin/ksh

_ARG1="${1}"
echo "Test 1A: The 1st argument is ${_ARG1-'ATC'}"
echo "Test 1B: The 1st argument is ${_ARG1:-'ATC'}"

_ARG2="${2}"
echo "Test 2A: The 2nd argument is ${_ARG2-'AMDC'}"
echo "Test 2B: The 2nd argument is ${_ARG2:-'AMDC'}"

_ARG3="${3}"
echo "Test 3A: The 3rd argument is ${_ARG3='PAC'}"
echo "Test 3B: The 3rd argument is ${_ARG3:='PAC'}"

_ARG4="${4}"
echo "Test 4A: ${4:+'The 4th argument was supplied'}"

echo "Test 5: If the 4th argument was provided, the value would be 
   ${4:?'The 4th argument was not supplied.'}. Otherwise, we will not 
   see this message and get an error instead."

_ARG8="${8}"
echo "${_ARG8:=${7:-${6:-${5:-No Arguments were supplied after the 4th}}}}"


Listing 19 shows how to execute the script with no argument supplied.

Listing 19. Execute the script without arguments

# ./subops_examples
Test 1A: The 1st argument is
Test 1B: The 1st argument is ATC
Test 2A: The 2nd argument is
Test 2B: The 2nd argument is AMDC
Test 3A: The 3rd argument is
Test 3B: The 3rd argument is PAC
Test 4A:
./subops_examples[18]: 4: The 4th argument was not supplied.


Listing 20 shows what happens when executing the script with only three arguments.

Listing 20. Execute the script with three arguments

# ./subops_examples arg1 arg2 arg3
Test 1A: The 1st argument is arg1
Test 1B: The 1st argument is arg1
Test 2A: The 2nd argument is arg2
Test 2B: The 2nd argument is arg2
Test 3A: The 3rd argument is arg3
Test 3B: The 3rd argument is arg3
Test 4A:
./subops_examples[18]: 4: The 4th argument was not supplied.


Listing 21 shows what happens when you supply only four arguments.

Listing 21. Execute the script with four arguments

# ./subops_examples arg1 arg2 arg3 arg4
Test 1A: The 1st argument is arg1
Test 1B: The 1st argument is arg1
Test 2A: The 2nd argument is arg2
Test 2B: The 2nd argument is arg2
Test 3A: The 3rd argument is arg3
Test 3B: The 3rd argument is arg3
Test 4A: The 4th argument was supplied
Test 5: If the 4th argument was provided, the value would be 
   arg4. Otherwise, we will not see this message and get an 
   error instead.
No Arguments were supplied after the 4th


Listing 22 shows all five arguments supplied.

Listing 22. Execute the script with all five arguments

# ./subops_examples arg1 arg2 arg3 arg4 arg5
Test 1A: The 1st argument is arg1
Test 1B: The 1st argument is arg1
Test 2A: The 2nd argument is arg2
Test 2B: The 2nd argument is arg2
Test 3A: The 3rd argument is arg3
Test 3B: The 3rd argument is arg3
Test 4A: The 4th argument was supplied
Test 5: If the 4th argument was provided, the value would be 
   arg4. Otherwise, we will not see this message and get an 
   error instead.
arg5


Listing 23 shows seven arguments supplied. Note how arguments 5 and 6 were ignored, because seven arguments were provided.

Listing 23. Execute the script with seven arguments

# ./subops_examples arg1 arg2 arg3 arg4 arg5 arg6 arg7
Test 1A: The 1st argument is arg1
Test 1B: The 1st argument is arg1
Test 2A: The 2nd argument is arg2
Test 2B: The 2nd argument is arg2
Test 3A: The 3rd argument is arg3
Test 3B: The 3rd argument is arg3
Test 4A: The 4th argument was supplied
Test 5: If the 4th argument was provided, the value would be 
   arg4. Otherwise, we will not see this message and get an 
   error instead.
arg7


Conclusion
After reading this article, you should have a better understanding of all those "strange" characters UNIX users are typing. Knowing how to redirect data as stdin or stdout, how to use the pipe, and how to use operators in UNIX helps you write more powerful scripts with better error trapping and cleaner logic. Good luck!



No comments:

Post a Comment