shell流程控制
if
if commands; thencommands
[elif commands; thencommands...]
[elsecommands]
fi
commands可以是test表达式[ expression ]
[expression]表达式(不推荐)
测试文件表达式
表达式 | 如果下列条件为真则返回True |
---|---|
file1 -ef file2 | file1 和 file2 拥有相同的索引号(通过硬链接两个文件名指向相同的文件)。 |
file1 -nt file2 | file1新于 file2。 |
file1 -ot file2 | file1早于 file2。 |
-b file | file 存在并且是一个块(设备)文件。 |
-c file | file 存在并且是一个字符(设备)文件。 |
-d file | file 存在并且是一个目录。 |
-e file | file 存在。 |
-f file | file 存在并且是一个普通文件。 |
-g file | file 存在并且设置了组 ID。 |
-G file | file 存在并且由有效组 ID 拥有。 |
-k file | file 存在并且设置了它的“sticky bit”。 |
-L file | file 存在并且是一个符号链接。 |
-O file | file 存在并且由有效用户 ID 拥有。 |
-p file | file 存在并且是一个命名管道。 |
-r file | file 存在并且可读(有效用户有可读权限)。 |
-s file | file 存在且其长度大于零。 |
-S file | file 存在且是一个网络 socket。 |
-t fd | fd 是一个定向到终端/从终端定向的文件描述符 。 这可以被用来决定是否重定向了标准输入/输出错误。 |
-u file | file 存在并且设置了 setuid 位。 |
-w file | file 存在并且可写(有效用户拥有可写权限)。 |
-x file | file 存在并且可执行(有效用户有执行/搜索权限) |
#!/bin/bash
# test-file: Evaluate the status of a file
FILE=~/.bashrc
if [ -e "$FILE" ]; thenif [ -f "$FILE" ]; thenecho "$FILE is a regular file."fiif [ -d "$FILE" ]; thenecho "$FILE is a directory."fiif [ -r "$FILE" ]; thenecho "$FILE is readable."fiif [ -w "$FILE" ]; thenecho "$FILE is writable."fiif [ -x "$FILE" ]; thenecho "$FILE is executable/searchable."fi
elseecho "$FILE does not exist"exit 1
fi
exit
exit与return
<font style="color:#3C3C3C;">exit </font>
命令接受一个单独的,可选的参数,其成为脚本的退出状态。当不传递参数时,退出状态默认为零。 以这种方式使用 exit 命令,则允许此脚本提示失败如果 $FILE
展开成一个不存在的文件名。这个 exit 命令出现在脚本中的最后一行,是一个当一个脚本“运行到最后”(到达文件末尾),不管怎样, 默认情况下它以退出状态零终止。
类似地,通过带有一个整数参数的 <font style="color:#3C3C3C;">return</font>
命令,shell 函数可以返回一个退出状态。如果我们打算把 上面的脚本转变为一个 shell 函数,为了在更大的程序中包含此函数,用 return 语句来代替 exit 命令
test_file () {# test-file: Evaluate the status of a fileFILE=~/.bashrcif [ -e "$FILE" ]; thenif [ -f "$FILE" ]; thenecho "$FILE is a regular file."fiif [ -d "$FILE" ]; thenecho "$FILE is a directory."fiif [ -r "$FILE" ]; thenecho "$FILE is readable."fiif [ -w "$FILE" ]; thenecho "$FILE is writable."fiif [ -x "$FILE" ]; thenecho "$FILE is executable/searchable."fielseecho "$FILE does not exist"return 1fi
}
测试字符串表达式
表达式 | 如果下列条件为真则返回True |
---|---|
string | string 不为 null。 |
-n string | 字符串 string 的长度大于零。 |
-z string | 字符串 string 的长度为零。 |
string1 = string2 string1 == string2 |
string1 和 string2 相同。 单或双等号都可以,不过双等号更受欢迎。 |
string1 != string2 | string1 和 string2 不相同。 |
string1 > string2 | sting1 排列在 string2 之后。 |
string1 < string2 | string1 排列在 string2 之前。 |
<font style="color:#3C3C3C;">></font>
和 <font style="color:#3C3C3C;"><</font>
表达式操作符必须用引号引起来(或者是用反斜杠转义)。如果不这样,它们会被 shell 解释为重定向操作符,造成潜在的破坏结果。
#!/bin/bash
# test-string: evaluate the value of a string
ANSWER=maybe
if [ -z "$ANSWER" ]; thenecho "There is no answer." >&2exit 1
fi
if [ "$ANSWER" = "yes" ]; thenecho "The answer is YES."
elif [ "$ANSWER" = "no" ]; thenecho "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; thenecho "The answer is MAYBE."
elseecho "The answer is UNKNOWN."
fi
这个脚本中,我们计算常量 ANSWER。我们首先确定是否此字符串为空。如果为空,我们就终止 脚本,并把退出状态设为1。注意这个应用于 echo 命令的重定向操作。其把错误信息 “There is no answer.” 重定向到标准错误,这是处理错误信息的“正确”方法。如果字符串不为空,我们就计算 字符串的值,看看它是否等于“yes,” “no,” 或者“maybe”。为此使用了 elif,它是 “else if” 的简写。
测试整数表达式
表达式 | 如果为真... |
---|---|
integer1 -eq integer2 | integer1 等于 integer2。 |
integer1 -ne integer2 | integer1 不等于 integer2。 |
integer1 -le integer2 | integer1 小于或等于 integer2。 |
integer1 -lt integer2 | integer1 小于 integer2。 |
integer1 -ge integer2 | integer1 大于或等于 integer2。 |
integer1 -gt integer2 | integer1 大于 integer2。 |
[[]] - test 命令替代物(推荐)
目前的 bash 版本包括一个复合命令,作为加强的 test 命令替代物。它使用以下语法:
[[ expression ]]
增加了一个重要的新的字符串表达式,其返回值为真,如果 string1匹配扩展的正则表达式 regex。这就为执行比如数据验证等任务提供了许多可能性。
string1 =~ regex
通过应用正则表达式,我们能够限制 INT 的值只是字符串,其开始于一个可选的减号,随后是一个或多个数字。 这个表达式也消除了空值的可能性。
#!/bin/bash
# test-integer2: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenif [ $INT -eq 0 ]; thenecho "INT is zero."elseif [ $INT -lt 0 ]; thenecho "INT is negative."elseecho "INT is positive."fiif [ $((INT % 2)) -eq 0 ]; thenecho "INT is even."elseecho "INT is odd."fifi
elseecho "INT is not an integer." >&2exit 1
fi
[[ ]]
添加的另一个功能是==
操作符支持类型匹配,正如路径名展开所做的那样
这就使[[ ]]
有助于计算文件和路径名。
if [[ $FILE == foo.* ]]; then
> echo "$FILE matches pattern 'foo.*'"
> fi
(( )) - 为整数设计(推荐)
除了 [[ ]]
复合命令之外,bash 也提供了 (( ))
复合命令,其有利于操作整数。它支持一套 完整的算术计算,
(( ))
被用来执行算术真测试。如果算术计算的结果是非零值,则其测试值为真。
$ if ((1)); then echo "It is true."; fi
It is true.
$ if ((0)); then echo "It is true."; fi
#!/bin/bash
# test-integer2a: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenif ((INT == 0)); thenecho "INT is zero."elseif ((INT < 0)); thenecho "INT is negative."elseecho "INT is positive."fiif (( ((INT % 2)) == 0)); thenecho "INT is even."elseecho "INT is odd."fifi
elseecho "INT is not an integer." >&2exit 1
fi
注意我们使用小于和大于符号,以及==用来测试是否相等。这是使用整数较为自然的语法了。也要 注意,因为复合命令 (( ))
是 shell 语法的一部分,而不是一个普通的命令,而且它只处理整数, 所以它能够通过名字识别出变量,而不需要执行展开操作。
表达式的逻辑运算
也有可能把表达式结合起来创建更复杂的计算。通过使用逻辑操作符来结合表达式。有三个用于 test 和 [[ ]]
的逻辑操作。 它们是 AND、OR 和 NOT。test 和 [[ ]]
使用不同的操作符来表示这些操作:
逻辑操作符
操作符 | 测试 | [[ ]] and (( )) |
---|---|---|
AND | -a | && |
OR | -o | |
NOT | ! | ! |
下面的脚本决定了一个整数是否属于某个范围内的值:
#!/bin/bash
# test-integer3: determine if an integer is within a
# specified range of values.
MIN_VAL=1
MAX_VAL=100
INT=50
if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenif [[ INT -ge MIN_VAL && INT -le MAX_VAL ]]; thenecho "$INT is within $MIN_VAL to $MAX_VAL."elseecho "$INT is out of range."fi
elseecho "INT is not an integer." >&2exit 1
fi
我们也可以对表达式使用圆括号,为的是分组。如果不使用括号,那么否定只应用于第一个 表达式,而不是两个组合的表达式。用 test 可以这样来编码:
if [ ! \( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL \) ]; thenecho "$INT is outside $MIN_VAL to $MAX_VAL."
elseecho "$INT is in range."
fi
因为 test 使用的所有的表达式和操作符都被 shell 看作是命令参数(不像 [[ ]]
和 (( ))
), 对于 bash 有特殊含义的字符,比如说 <,>,(,和 ),必须引起来或者是转义。
知道了 test 和 [[ ]]
基本上完成相同的事情,哪一个更好呢?test 更传统(是 POSIX 的一部分), 然而 [[ ]]
特定于 bash。知道怎样使用 test 很重要,因为它被非常广泛地应用,但是显然 [[ ]]
更 有用,并更易于编码。
通过&&||实现条件判断
command1 && command2
command1 || command2
$ mkdir temp && cd temp
# 这会创建一个名为 temp 的目录,并且若它执行成功后,当前目录会更改为 temp。第二个命令会尝试 执行只有当 mkdir 命令执行成功之后。
$ [ -d temp ] || mkdir temp
# 会测试目录 temp 是否存在,并且只有测试失败之后,才会创建这个目录。这种构造类型非常有助于在 脚本中处理错误
[ -d temp ] || exit 1
# 如果这个脚本要求目录 temp,且目录不存在,然后脚本会终止,并返回退出状态1。
while/until循环
while
while commands; do commands; done
#!/bin/bash
count=1
while [ $count -le 5 ]; doecho $countcount=$((count + 1))
done
echo "Finished."
只要退出状态为零,它就执行循环内的命令。 在上面的脚本中,创建了变量 count ,并初始化为1。 while 命令将会计算 test 命令的退出状态。 只要 test 命令返回退出状态零,循环内的所有命令就会执行。每次循环结束之后,会重复执行 test 命令。 第六次循环之后, count 的数值增加到6, test 命令不再返回退出状态零,且循环终止。
#!/bin/bash
DELAY=3 # Number of seconds to display results
# while [[ $REPLY != 0 ]]; do 使用了break这个就没有必要了
while true; doclearcat <<- _EOF_Please Select:1. Display System Information2. Display Disk Space3. Display Home Space Utilization0. Quit_EOF_read -p "Enter selection [0-3] > "if [[ $REPLY =~ ^[0-3]$ ]]; thenif [[ $REPLY == 1 ]]; thenecho "Hostname: $HOSTNAME"uptimesleep $DELAYcontinuefiif [[ $REPLY == 2 ]]; thendf -hsleep $DELAYcontinuefiif [[ $REPLY == 3 ]]; thenif [[ $(id -u) -eq 0 ]]; thenecho "Home Space Utilization (All Users)"du -sh /home/*elseecho "Home Space Utilization ($USER)"du -sh $HOMEfisleep $DELAYcontinuefiif [[ $REPLY == 0 ]]; thenbreakfielseecho "Invalid entry."sleep $DELAYfi
done
echo "Program terminated."
until
until 命令与 while 非常相似,除了当遇到一个非零退出状态的时候, while 退出循环, 而 until 不退出。一个 until 循环会继续执行直到它接受了一个退出状态零。
#!/bin/bash
# until-count: display a series of numbers
count=1
until [ $count -gt 5 ]; doecho $countcount=$((count + 1))
done
echo "Finished."
使用循环读取文件
#!/bin/bash
# while-read: read lines from a file
while read distro version release; doprintf "Distro: %s\tVersion: %s\tReleased: %s\n" \$distro \$version \$release
done < distros.txt
为了重定向文件到循环中,我们把重定向操作符放置到 done 语句之后。循环将使用 read 从重定向文件中读取 字段。这个 read 命令读取每个文本行之后,将会退出,其退出状态为零,直到到达文件末尾。到时候,它的退出状态为非零数值,因此终止循环。也有可能把标准输入管道到循环中。
#!/bin/bash
# while-read2: read lines from a file
sort -k 1,1 -k 2n distros.txt | while read distro version release; doprintf "Distro: %s\tVersion: %s\tReleased: %s\n" \$distro \$version \$release
done
这里我们接受 sort 命令的标准输出,然后显示文本流。然而,因为管道将会在子 shell 中执行 循环,当循环终止的时候,循环中创建的任意变量或赋值的变量都会消失,
case
case word in[pattern [| pattern]...) commands ;;]...
esac
case 语句使用的模式pattern 和路径展开中使用的那些是一样的。模式以一个 “)” 为终止符
#!/bin/bash
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "
case $REPLY in0) echo "Program terminated."exit;;1) echo "Hostname: $HOSTNAME"uptime;;2) df -h;;3) if [[ $(id -u) -eq 0 ]]; thenecho "Home Space Utilization (All Users)"du -sh /home/*elseecho "Home Space Utilization ($USER)"du -sh $HOMEfi;;*) echo "Invalid entry" >&2exit 1;;
esac
case 模式
模式 | 描述 |
---|---|
a) | 若单词为 “a”,则匹配 |
[[:alpha:]]) | 若单词是一个字母字符,则匹配 |
???) | 若单词只有3个字符,则匹配 |
*.txt) | 若单词以 “.txt” 字符结尾,则匹配 |
*) | 匹配任意单词。把这个模式做为 case 命令的最后一个模式,是一个很好的做法, 可以捕捉到任意一个与先前模式不匹配的数值;也就是说,捕捉到任何可能的无效值。 |
#!/bin/bash
read -p "enter word > "
case $REPLY in[[:alpha:]]) echo "is a single alphabetic character." ;;[ABC][0-9]) echo "is A, B, or C followed by a digit." ;;???) echo "is three characters long." ;;*.txt) echo "is a word ending in '.txt'" ;;*) echo "is something else." ;;
esac
还可以使用竖线字符作为分隔符,把多个模式结合起来。这就创建了一个 “或” 条件模式。
#!/bin/bash
# case-menu: a menu driven system information program
clear
echo "
Please Select:
A. Display System Information
B. Display Disk Space
C. Display Home Space Utilization
Q. Quit
"
read -p "Enter selection [A, B, C or Q] > "
case $REPLY in
q|Q) echo "Program terminated."exit;;
a|A) echo "Hostname: $HOSTNAME"uptime;;
b|B) df -h;;
c|C) if [[ $(id -u) -eq 0 ]]; thenecho "Home Space Utilization (All Users)"du -sh /home/*elseecho "Home Space Utilization ($USER)"du -sh $HOMEfi;;
*) echo "Invalid entry" >&2exit 1;;
esac
添加的 “;;&” 的语法允许 case 语句继续执行下一条测试,而不是简单地终止运行。
#!/bin/bash
# case4-2: test a character
read -n 1 -p "Type a character > "
echo
case $REPLY in[[:upper:]]) echo "'$REPLY' is upper case." ;;&[[:lower:]]) echo "'$REPLY' is lower case." ;;&[[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&[[:digit:]]) echo "'$REPLY' is a digit." ;;&[[:graph:]]) echo "'$REPLY' is a visible character." ;;&[[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&[[:space:]]) echo "'$REPLY' is a whitespace character." ;;&[[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
esac
for
for: 传统 shell 格式
for variable [in words]; docommands
done
variable这个变量在循环执行期间会增加,words 是一个可选的条目列表其值会按顺序赋值给 variable,
花括号展开:
$ for i in {A..D}; do echo $i; done
A
B
C
D
路径名展开:
$ for i in distros*.txt; do echo $i; done
distros-by-date.txt
distros-dates.txt
distros-key-names.txt
distros-key-vernums.txt
distros-names.txt
distros.txt
distros-vernums.txt
distros-versions.txt
命令替换:
#!/bin/bash
while [[ -n $1 ]]; doif [[ -r $1 ]]; thenmax_word=max_len=0for i in $(strings $1); dolen=$(echo $i | wc -c)if (( len > max_len )); thenmax_len=$lenmax_word=$ifidoneecho "$1: '$max_word' ($max_len characters)"fishift
done
在这个示例中,我们要在一个文件中查找最长的字符串。当在命令行中给出一个或多个文件名的时候, 该程序会使用 strings 程序(其包含在 GNU binutils 包中),为每一个文件产生一个可读的文本格式的 “words” 列表。 然后这个 for 循环依次处理每个单词,判断当前这个单词是否为目前为止找到的最长的一个。当循环结束的时候,显示出最长的单词。
如果省略掉 for 命令的可选项 words 部分,for 命令会默认处理位置参数()。
#!/bin/bash
for i; doif [[ -r $i ]]; thenmax_word=max_len=0for j in $(strings $i); dolen=$(echo $j | wc -c)if (( len > max_len )); thenmax_len=$lenmax_word=$jfidoneecho "$i: '$max_word' ($max_len characters)"fi
done
正如我们所看到的,我们已经更改了最外围的循环,用 for 循环来代替 while 循环。通过省略 for 命令的 words 列表, 用位置参数替而代之。在循环体内,之前的变量 i 已经改为变量 j。同时 shift 命令也被淘汰掉了。
for: C 语言格式
最新版本的 bash 已经添加了第二种格式的 for 命令语法,该语法相似于 C 语言中的 for 语法格式。 其它许多编程语言也支持这种格式:
for (( expression1; expression2; expression3 )); docommands
done
这里的 expression1、expression2和 expression3 都是算术表达式,
在行为方面,这相当于以下构造形式:
(( expression1 ))
while (( expression2 )); docommands(( expression3 ))
done
expression1 用来初始化循环条件,expression2 用来决定循环结束的时间,还有在每次循环迭代的末尾会执行 expression3。
#!/bin/bash
for (( i=0; i<5; i=i+1 )); doecho $i
done
实例
report_home_space () {if [[ $(id -u) -eq 0 ]]; thencat <<- _EOF_<H2>Home Space Utilization (All Users)</H2><PRE>$(du -sh /home/*)</PRE>_EOF_elsecat <<- _EOF_<H2>Home Space Utilization ($USER)</H2><PRE>$(du -sh $HOME)</PRE>_EOF_fireturn
}
下一步,我们将重写它,以便提供每个用户家目录的更详尽信息,并且包含用户家目录中文件和目录的总个数:
report_home_space () {local format="%8s%10s%10s\n"local i dir_list total_files total_dirs total_size user_nameif [[ $(id -u) -eq 0 ]]; thendir_list=/home/*user_name="All Users"elsedir_list=$HOMEuser_name=$USERfiecho "<H2>Home Space Utilization ($user_name)</H2>"for i in $dir_list; dototal_files=$(find $i -type f | wc -l)total_dirs=$(find $i -type d | wc -l)total_size=$(du -sh $i | cut -f 1)echo "<H3>$i</H3>"echo "<PRE>"printf "$format" "Dirs" "Files" "Size"printf "$format" "----" "-----" "----"printf "$format" $total_dirs $total_files $total_sizeecho "</PRE>"donereturn
}
在 if 语句块内设置了一些随后会在 for 循环中用到的变量,来取代在 if 语句块内执行完备的动作集合。我们给 函数添加了几个本地变量,并且使用 printf 来格式化输出。