0%

Shell条件判断

条件语句

1
2
3
4
5
if commandt
then
command1
command2
fi

0表示成功

Exit Status

要了解条件测试如何工作,了解 Unix 如何处理所谓的退出状态非常重要,无论什么时候任何程序执行完毕都会向 shell 返回 exit status code。这个退出码是一个数字来指代程序是否成功运行。通常 0 表示成功,非 0 表示失败。

程序失败的原因有很多,通常使用不同的非 0 状态表示不同类型的错误。

在一个管道中,退出码通常是最后一个命令产生的,who | grep fred,shell 使用 grep 的返回值来表示整个管道的退出码。

$?

shell 变量 $? 由 shell 自动设置为最后执行的命令的退出状态。

echo $?

发生错误时的返回值在不同版本的 Unix 中可能不同,但是执行成功的返回值总是 0。

1
2
3
4
5
6
7
8
9
10
# on
#
# determine if someone is logged on
#
user="$1"

if who | grep "$user"
then
echo "$user is logged on"
fi

判断条件语句的返回值是否为 0,如果是则表示成功执行对应的 then 语句。如果返回值为非 0,则 echo 不会执行。

echo 命令使用缩进只是为了代码的可读性。

执行程序后会发现几个问题,当返回值为 0 时,who | grep $user 的结果会打印出来。可以重定向到 /dev/null

仅仅使用 grep $user 是不够的,会匹配到其他用户。

1
2
3
4
5
6
7
8
9
10
# on
#
# determine if someone is logged on -- version 2
#
user="$1"

if who | grep "^$user " > /dev/null
then
echo "$user is logged on"
fi

test

test expressiontest 会评估 expression 的结果,如果结果是 true,那么 test 返回 0,否则返回非 0。

String Operators

test "$name" = abc,注意 test 必须能看到所有的 operands($name 和 abc)和 operators作为单独的参数,这就意味着需要用空白字符分隔。

Operator return TRUE(exit status of 0) if
string1 = string2 两个字符串相等
string1 != string2 两个字符串不相等
string string is not null
-n string string is not null (string must be seen by test)
-z string string is null (string must be seen by test)
1
2
3
4
5
6
7
$ blanks="    "
$ test $blanks
$ echo $?
1
$ test "$blanks"
$ echo $?
0

第一个例子中,shell 会去掉多余的空格,就像 echo one two 一样,第二个例子中则会包含空格。

关于空白字符和引号是 shell 编程中最容易产生错误的地方。

可以任务 -n-z 是对字符串长度的判断。

1
2
3
4
5
$ echo $symbol
=
$ test -z "$symbol" # 书中会报错但是自己测试不报错
sh: test: argument expected
test X"$symbol" = X # X 防止 shell 把 symbol 解析为 = operator

An Alternative Format for test

test expression 等价于 [ expression ],注意空格要保留,推荐使用这种语法。

Integer Operator

-eq -ge -gt -le -lt -ne[ "$count" -eq 0 ]

值得注意的是当使用整数操作符时,是 test 命令复杂把变量解析为整数的,而不是 shell。

1
2
3
4
5
6
x1="005"
x2=" 10"
[ "$x1" = 5 ] # str comparison false
[ "$x1" -eq 5 ] # integer comparison true
[ "$x2" = 10 ] # str comparison false
[ "$x2" -eq 10 ] # integer comparison true

File Operators

Operator return TRUE(exit status of 0) if
-d file file is a directory
-e file file exists
-f file file is an ordinary file
-r file file is readable by the process
-s file file has nonzero length
-w file file is writeable by the process
-x file file is executable
-L file file is a symbolic link

The Logical Negation Operator

[ ! -r /home/smith/phonebook ][ ! "$x1" = "$x2" ][ $"x1" != "$x2" ] 效果一样。

The Logical AND Operator -a

[ -f "$mailfile" -a -r "$mailfile" ]

[ "$count" -ge 0 -a "$count" -lt 10 ] -a 优先级比整数比较操作符低。

短路,只要遇到一个返回值是 false,接下来的判断就不会执行。

[ ! -f "$file" -a $(who > $file) ],如果前面的判断是 false,后面的就不会执行。

Parentheses

可以使用括号明确指定执行顺序。[ \( "$count" -ge 0 \) -a \( "$count" -lt 10 \) ]

The Logical OR Operator -o

[ -n "$mailopt" -o -r $HOME/mailfile ]-o 的优先级比 -a 低。

"$a" -eq 0 -o "$b" -eq 2 -a "$c" -eq 10 等价于 "$a" -eq 0 -o ("$b" -eq 2 -a "$c" -eq 10)

The else

1
2
3
4
5
6
7
8
9
10
11
12
13
# on
#
# determine if someone is logged on -- version 3
#

user="$1"

if who | grep "^$user " > /dev/null
then
echo "$user is logged on"
else
echo "$user is not logged on"
fi

要把原型转换成能够长期使用的程序,需要确保正确的参数数量传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# on
#
# determine if someone is logged on -- version 4
#
if [ "$#" -ne 1 ]
then
echo "Incorrect number of args"
echo "Usage: on user"
else
user="$1"
if who | grep "^$user " > /dev/null
then
echo "$user is logged on"
else
echo "$user is not logged on"
fi
fi

The exit Command

built in command exit 能让程序立刻退出。exit n,如果 n 未提供,那么返回最后一次执行的命令的返回值,相当于 exit $?

如果在命令行直接执行则会退出相应的 shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
# rem
#
# Remove someone from the phone book -- version 2
#
if [ "$#" -ne 1 ]
then
echo "Incorrect number of args"
echo "Usage: rem name"
exit 1
fi

grep -v "$1" phonebook > /tmp/phonebook
mv /tmp/phonebook phonebook

The elif Construct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# greetings
#
# Program to print a greeting
#
hour=$(date | cut -c12-13)
if [ "$hour" -ge 0 -a "$hour" -le 11 ]
then
echo "Good morning"
else
if [ "$hour" -ge 12 -a "$hour" -le 17 ]
then
echo "Good afternoon"
else
echo "Good evening"
fi
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# greetings
#
# Program to print a greeting -- version 2
#
hour=$(date | cut -c12-13)
if [ "$hour" -ge 0 -a "$hour" -le 11 ]
then
echo "Good morning"
elif [ "$hour" -ge 12 -a "$hour" -le 17 ]
then
echo "Good afternoon"
else
echo "Good evening"
fi

一个重要的改善,程序更易读了。

一般不使用 date | cut -c12-13,而是 date +%H

rem 改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# rem
#
# Remove someone from the phone book -- verison 3
#
if [ "$#" -ne 1 ]
then
echo "Incorrect number of args."
echo "Usage: rem name"
exit 1
fi
name=$1
matches=$(grep "$name" phonebook | wc -l)
if [ "$matches" -gt 1 ]
then
echo "more than one match;please qualify further"
elif [ "$matches" -eq 1 ]
then
grep -v "$name" phonebook > /tmp/phonebook
mv /tmp/phonebook phonebook
else
echo "I couldn't find $name in the phone book"

The case command

1
2
3
4
5
6
case v in
pattern1)
command ;;
pattern2)
command ;;
esac

pattern 可以是正则表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ctype
#
# Classify character given as argument
#
if [ $# -ne 1 ]
then
echo Usage: ctype character
exit 1
fi

char="$1"
numchars=$(echo "$char" | wc -c)

if [ "$numchars" -ne 1 ]
then
echo Please type a single character
exit 1
fi

case "$char"
in
[0-9] ) echo digit;;
[a-z] ) echo lowercase letter;;
[A-Z] ) echo uppercase letter;;
* ) echo special character;;
esac

bug?

The -x Option for Debugging Programs

sh -x ctype a,发现 echo "$char" | wc -c 结果是2,包含了换行符号。要修改条件为 [ "$numchars" -ne 2 ]

new version avoid use wc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if [ $# -ne 1 ]
then
echo Usage: ctype character
exit 1
fi

char="$1"

case "$char"
in
[0-9] ) echo digit;;
[a-z] ) echo lowercase letter;;
[A-Z] ) echo uppercase letter;;
? ) echo special character;;
* ) echo Please type a single character;;
esac
1
2
3
4
5
6
7
8
9
10
11
12
# greetings
#
# Program to print a greeting -- case version
#
hour=$(date +%H)

case "$hour"
in
0? | 1[01] ) echo "Good morning";;
1[2-7] ) echo "Good afternoon";;
* ) echo "Good evening";;
esac

The Null Command

空命令,类似于 python 中的 pass。

The && and || Constructs

shell 有两个特殊的结构,能够根据前面的命令是成功还是失败来执行命令。是 if 语句的简写形式。

commad1 && command2,第一个命令返回 0 才执行第二个命令,如果是非 0,那么忽略第二个命令。

commad1 || command2,第一个命令返回非 0 才执行第二个命令,如果是 0,那么忽略第二个命令。