7.2. 更高级的 if 用法

7.2.1. if/then/else 结构

7.2.1.1. 虚拟示例

这是用于在 if 命令测试为真时采取一种行动方案,而在测试为假时采取另一种行动方案的结构。一个例子

freddy scripts> gender="male"

freddy scripts> if [[ "$gender" == "f*" ]]
More input> then echo "Pleasure to meet you, Madame."
More input> else echo "How come the lady hasn't got a drink yet?"
More input> fi
How come the lady hasn't got a drink yet?

freddy scripts>

Important[] 与 [[]]
 

与此相反的是[, [[防止变量值进行单词分割。所以,如果VAR="var with spaces",你不需要用双引号括起来$VAR在测试中 - 尽管使用引号仍然是一个好习惯。此外,[[防止路径名扩展,因此带有通配符的字面字符串不会尝试扩展为文件名。使用[[, ==!=将右侧的字符串解释为要与左侧值匹配的 shell glob 模式,例如[[ "value" == val* ]].

then 语句后面的 CONSEQUENT-COMMANDS 列表一样,else 语句后面的 ALTERNATE-CONSEQUENT-COMMANDS 列表可以容纳任何返回退出状态的 UNIX 风格命令。

另一个例子,扩展了 第 7.1.2.1 节 中的例子

anny ~> su -
Password:
[root@elegance root]# if ! grep ^$USER /etc/passwd 1> /dev/null
> then echo "your user account is not managed locally"
> else echo "your account is managed from the local /etc/passwd file"
> fi
your account is managed from the local /etc/passwd file
[root@elegance root]#

我们切换到 root 帐户来演示 else 语句的效果 - 你的 root 通常是一个本地帐户,而你自己的用户帐户可能由中央系统(如 LDAP 服务器)管理。

7.2.1.2. 检查命令行参数

与其设置一个变量然后执行脚本,通常更优雅的做法是将变量的值放在命令行上。

我们使用位置参数$1, $2, ..., $N为此目的。$#指的是命令行参数的数量。$0指的是脚本的名称。

以下是一个简单的例子

图 7-1. 使用 if 测试命令行参数

这是另一个例子,使用两个参数

anny ~> cat weight.sh
#!/bin/bash

# This script prints a message about your weight if you give it your
# weight in kilos and height in centimeters.

weight="$1"
height="$2"
idealweight=$[$height - 110]

if [ $weight -le $idealweight ] ; then
  echo "You should eat a bit more fat."
else
  echo "You should eat a bit more fruit."
fi

anny ~> bash -x weight.sh 55 169
+ weight=55
+ height=169
+ idealweight=59
+ '[' 55 -le 59 ']'
+ echo 'You should eat a bit more fat.'
You should eat a bit more fat.

7.2.1.3. 测试参数的数量

下面的例子展示了如何更改之前的脚本,以便在给出多于或少于 2 个参数时打印消息

anny ~> cat weight.sh
#!/bin/bash

# This script prints a message about your weight if you give it your
# weight in kilos and height in centimeters.

if [ ! $# == 2 ]; then
  echo "Usage: $0 weight_in_kilos length_in_centimeters"
  exit
fi

weight="$1"
height="$2"
idealweight=$[$height - 110]

if [ $weight -le $idealweight ] ; then
  echo "You should eat a bit more fat."
else
  echo "You should eat a bit more fruit."
fi

anny ~> weight.sh 70 150
You should eat a bit more fruit.

anny ~> weight.sh 70 150 33
Usage: ./weight.sh weight_in_kilos length_in_centimeters

第一个参数被称为$1,第二个参数被称为$2等等。参数的总数存储在$#.

查看 第 7.2.5 节,了解更优雅的打印用法消息的方式。

7.2.1.4. 测试文件是否存在

这个测试在很多脚本中都会用到,因为如果你知道它们不会工作,那么启动很多程序是没有用的

#!/bin/bash

# This script gives information about a file.

FILENAME="$1"

echo "Properties for $FILENAME:"

if [ -f $FILENAME ]; then
  echo "Size is $(ls -lh $FILENAME | awk '{ print $5 }')"
  echo "Type is $(file $FILENAME | cut -d":" -f2 -)"
  echo "Inode number is $(ls -i $FILENAME | cut -d" " -f1 -)"
  echo "$(df -h $FILENAME | grep -v Mounted | awk '{ print "On",$1", \
which is mounted as the",$6,"partition."}')"
else
  echo "File does not exist."
fi

请注意,文件是通过变量引用的;在本例中,它是脚本的第一个参数。或者,当没有给出参数时,文件位置通常存储在脚本开头的变量中,并且它们的内容通过这些变量引用。因此,当你想在脚本中更改文件名时,你只需要更改一次。

Tip带有空格的文件名
 

如果$1的值可以解析为多个单词,则上述示例将失败。在这种情况下,if 命令可以通过在文件名周围使用双引号,或者通过使用[[而不是[.

7.2.2. if/then/elif/else 结构

7.2.2.1. 概述

这是 if 语句的完整形式

if TEST-COMMANDS; then

CONSEQUENT-COMMANDS;

elif MORE-TEST-COMMANDS; then

MORE-CONSEQUENT-COMMANDS;

else ALTERNATE-CONSEQUENT-COMMANDS;

fi

TEST-COMMANDS 列表被执行,如果它的返回状态为零,则执行 CONSEQUENT-COMMANDS 列表。如果 TEST-COMMANDS 返回非零状态,则依次执行每个 elif 列表,如果其退出状态为零,则执行相应的 MORE-CONSEQUENT-COMMANDS,并且命令完成。如果 else 后跟一个 ALTERNATE-CONSEQUENT-COMMANDS 列表,并且最后一个 ifelif 子句中的最后一个命令具有非零退出状态,则执行 ALTERNATE-CONSEQUENT-COMMANDS。返回状态是最后执行的命令的退出状态,如果没有任何条件测试为真,则为零。

7.2.2.2. 示例

这是一个你可以放在 crontab 中每日执行的例子

anny /etc/cron.daily> cat disktest.sh
#!/bin/bash

# This script does a very simple test for checking disk space.

space=`df -h | awk '{print $5}' | grep % | grep -v Use | sort -n | tail -1 | cut -d "%" -f1 -`
alertvalue="80"

if [ "$space" -ge "$alertvalue" ]; then
  echo "At least one of my disks is nearly full!" | mail -s "daily diskcheck" root
else
  echo "Disk space normal" | mail -s "daily diskcheck" root
fi

7.2.3. 嵌套的 if 语句

if 语句内部,你可以使用另一个 if 语句。你可以使用任意多层嵌套的 if 语句,只要你在逻辑上能够管理。

这是一个测试闰年的例子

anny ~/testdir> cat testleap.sh
#!/bin/bash
# This script will test if we're in a leap year or not.

year=`date +%Y`

if [ $[$year % 400] -eq "0" ]; then
  echo "This is a leap year.  February has 29 days."
elif [ $[$year % 4] -eq 0 ]; then
        if [ $[$year % 100] -ne 0 ]; then
          echo "This is a leap year, February has 29 days."
        else
          echo "This is not a leap year.  February has 28 days."
        fi
else
  echo "This is not a leap year.  February has 28 days."
fi

anny ~/testdir> date
Tue Jan 14 20:37:55 CET 2003

anny ~/testdir> testleap.sh
This is not a leap year.

7.2.4. 布尔运算

上面的脚本可以使用布尔运算符 "AND" (&&) 和 "OR" (||) 缩短。

图 7-2. 使用布尔运算符的示例

我们使用双括号来测试算术表达式,请参阅 第 3.4.6 节。这等同于 let 语句。如果你尝试类似 $[$year % 400] 的操作,你将会在方括号这里遇到困难,因为在这里,方括号本身并不代表一个实际的命令。

在其他编辑器中,gvim 是其中一个支持根据文件格式进行颜色方案的编辑器;这种编辑器对于检测代码中的错误很有用。

7.2.5. 使用 exit 语句和 if

我们在 第 7.2.1.3 节 中已经简要地了解了 exit 语句。它终止整个脚本的执行。如果从用户请求的输入不正确,如果语句未成功运行,或者如果发生其他错误,则最常使用它。

exit 语句接受一个可选参数。此参数是整数退出状态代码,它被传递回父进程并存储在$?变量中。

零参数表示脚本成功运行。程序员可以使用任何其他值将不同的消息传递回父进程,以便可以根据子进程的失败或成功采取不同的操作。如果没有为 exit 命令提供参数,则父 shell 使用当前的$?变量中。

下面是一个稍作调整的penguin.sh脚本的示例,该脚本将其退出状态发送回父进程,feed.sh:

anny ~/testdir> cat penguin.sh
#!/bin/bash
                                                                                                 
# This script lets you present different menus to Tux.  He will only be happy
# when given a fish.  We've also added a dolphin and (presumably) a camel.
                                                                                                 
if [ "$menu" == "fish" ]; then
  if [ "$animal" == "penguin" ]; then
    echo "Hmmmmmm fish... Tux happy!"
  elif [ "$animal" == "dolphin" ]; then
    echo "Pweetpeettreetppeterdepweet!"
  else
    echo "*prrrrrrrt*"
  fi
else
  if [ "$animal" == "penguin" ]; then
    echo "Tux don't like that.  Tux wants fish!"
    exit 1
  elif [ "$animal" == "dolphin" ]; then
    echo "Pweepwishpeeterdepweet!"
    exit 2
  else
    echo "Will you read this sign?!"
    exit 3
  fi
fi

此脚本在下一个脚本中被调用,因此导出其变量menuanimal:

anny ~/testdir> cat feed.sh
#!/bin/bash
# This script acts upon the exit status given by penguin.sh
                                                                                                 
export menu="$1"
export animal="$2"
                                                                                                 
feed="/nethome/anny/testdir/penguin.sh"
                                                                                                 
$feed $menu $animal
                                                                                                 
case $? in
                                                                                                 
1)
  echo "Guard: You'd better give'm a fish, less they get violent..."
  ;;
2)
  echo "Guard: It's because of people like you that they are leaving earth all the time..."
  ;;
3)
  echo "Guard: Buy the food that the Zoo provides for the animals, you ***, how
do you think we survive?"
  ;;
*)
  echo "Guard: Don't forget the guide!"
  ;;
esac
                                                                                                 
anny ~/testdir> ./feed.sh apple penguin
Tux don't like that.  Tux wants fish!
Guard: You'd better give'm a fish, less they get violent...

正如你所看到的,退出状态代码可以自由选择。现有命令通常有一系列定义的代码;有关更多信息,请参阅每个命令的程序员手册。