הלינקייה: מגזין חודשי למפתחים

רוצה לשמוע על כל האירועים, המדריכים, הקורסים והמאמרים שנכתבו החודש ?
הלינקייה הינו מגזין חופשי בעברית שמשאיר אותך בעניינים.
בלי ספאם. בלי שטויות. פעם בחודש אצלך בתיבה.

Control Flow

Being a mini programming language, the shell provides us with the basic control structures
we have learned to expect in programming, and a few more.

In this chapter, we explore the various control structures available in bash.

The bourne shell uses square brackets to denote boolean expressions. Inside a bracket
conditional the following operators are supported:

  • ! expr true if expr is false
  • expr1 -a expr2 true if both expr1 and expr2 are true
  • expr1 -o expr2 true if either expr1 is true, or expr2 is true

Looping Constructs

Bash has the standard while, for and until loops. Here's how we use them:

# Reading text strings from a user and touches the files.
# Stops on an empty input
# note the ; do in the end of the line, and the done to finish
# the loop block

read -p 'Next file name (or blank to finish): ' filename

while [[ $filename ]]; do
  touch $filename
  read -p 'Next file name (or blank to finish): ' filename
done

# Wait for a specific username to go online
username=$1

while [[ ! `who | grep -w $username` ]]; do
  echo "Still waiting for $username"
  sleep 1
done

echo "User $username online"

# The for loops come in two flavors - either as a c-style for loop
# or as a foreach.
# Here are a couple of examples

# We start with the classic "print numbers 1 to 10"
for (( i=0; i<10; ++i )); do
  echo $i
done

# And a foreach loop that prints the sum of all file
# sizes in the directory
# Note the 'let' keyword for performing shell arithmetics

sum=0

for file in *; do
  let sz=`ls -l $file | tr -s ' ' | cut -d ' ' -f 5`
  let sum+=$sz
done

echo $sum

Making Decisions

Bash has more than enough conditional constructs to help us branch to our desired
condition. In this section we'll see how to use if, case and select.
Keep in mind the same bracket notation for boolean expressions we used earlier is still
relevant here.

# Print the largest file name in the directory
# Note how we compare with normal parenthesis. The reason is we
# need numeric comparison (a bracket notation compares strings).

max=-1

for file in *; do
  let sz=`ls -l $file | tr -s ' ' | cut -d ' ' -f 5`
  echo "sz = $sz, max = $max"
  if (( $sz > $max )); then
    let max=$sz
    winner=$file
  fi
done

echo "And the winner is... $winner"

# An example from the bash man page for using the case builtin:

  echo -n "Enter the name of an animal: "
  read ANIMAL
  echo -n "The $ANIMAL has "
  case $ANIMAL in
    horse | dog | cat) echo -n "four";;
    man | kangaroo ) echo -n "two";;
    *) echo -n "an unknown number of";;
  esac
  echo " legs."

select command is used to ask for some information out of a given list of options. The
original line a user has entered is stored in $REPLY, and if it is a word from the list of
possible selections, it is also stored in the name variable.

# The select builtin is used to display a menu to the user
# For example, the following snippet asks the user to select a file,
# then prints out its name.
  select fname in *;
  do
    echo you picked $fname \($REPLY\)
    break;
  done

Choosing the right brackets

All flow control structures in bash take a command as their first argument. This is counter intuitive and different from any other programming language - usually, a loop or an if tests on a value. This, of course, makes sense if we keep in mind that bash is designed to work as a command shell, which means its primary objective is to run programs.
The simplest control structure takes a simple command and checks its return value, so for example in the following block will run the cd command, check the value of $? and only if it is 0 the echo is executed:

if cd /sunshine; then
  echo "Im walking on Sunshine"
fi

What about the brackets, then ? Well, brackets are just commands in bash. A square bracket is equivalent to the command test, which takes an expressions and sets $? to 0 if that expression is true. The parenthesis is a special bash command that takes a numeric expression, executes it, and sets $? to 0 if the value of the expression is not zero.
A square bracket is best fitted to Boolean or file system tests, so all the following are valid:

# if /foo is a file echo 0, otherwise echo 1
[[ -f /foo ]] ; echo $?

# if / is a dir (usually it is) echo 0, otherwise echo 1
[[ -d / ]];      echo $?

# if a's ascii value is lower than b's ascii value (which it is) - echo 0
[[ a < b ]];   echo $?

# if the variable $x has the value 9 (note both = and == work the same)
[[ $x = 9 ]]; echo $?

The parenthesis can take a numeric expression to calculate and set $? to whether that expression was not zero. All the following are valid:

# echo 1
(( 1-1 )); echo $?

# echo 0
(( 1+1 )); echo $?

# echo 0 (Note that square brackets would yield a different result)
(( 7 < 10 )); echo $?

# echos 1 if $x is non-zero
(( $x )); echo $?

# calculations within (( )) also work, so this is valid while loop. Note not using the $ because the x is an lvalue
# prints all numbers from 9 to 0
x=10
while (( x-- )); do
  echo $x
done

Parenthesis are good at giving us the feeling we're in c, but we must remember that they're really just a command.
Therefore, we'll use square brackets when testing for conditionals or file system operations, and use the parenthesis for numeric calculations.