shell流程控制
shell流程控制
if
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
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" ]; then
if [ -f "$FILE" ]; then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ]; then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable."
fi
if [ -x "$FILE" ]; then
echo "$FILE is executable/searchable."
fi
else
echo "$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 file
FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ]; then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable."
fi
if [ -x "$FILE" ]; then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist"
return 1
fi
}
测试字符串表达式
| 表达式 | 如果下列条件为真则返回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" ]; then
echo "There is no answer." >&2
exit 1
fi
if [ "$ANSWER" = "yes" ]; then
echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
echo "The answer is MAYBE."
else
echo "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]+$ ]]; then
if [ $INT -eq 0 ]; then
echo "INT is zero."
else
if [ $INT -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0 ]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 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]+$ ]]; then
if ((INT == 0)); then
echo "INT is zero."
else
if ((INT < 0)); then
echo "INT is negative."
else
echo "INT is positive."
fi
if (( ((INT % 2)) == 0)); then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 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]+$ ]]; then
if [[ INT -ge MIN_VAL && INT -le MAX_VAL ]]; then
echo "$INT is within $MIN_VAL to $MAX_VAL."
else
echo "$INT is out of range."
fi
else
echo "INT is not an integer." >&2
exit 1
fi
我们也可以对表达式使用圆括号,为的是分组。如果不使用括号,那么否定只应用于第一个 表达式,而不是两个组合的表达式。用 test 可以这样来编码:
if [ ! \( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL \) ]; then
echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
echo "$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 ]; do
echo $count
count=$((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; do
clear
cat <<- _EOF_
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
_EOF_
read -p "Enter selection [0-3] > "
if [[ $REPLY =~ ^[0-3]$ ]]; then
if [[ $REPLY == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
sleep $DELAY
continue
fi
if [[ $REPLY == 2 ]]; then
df -h
sleep $DELAY
continue
fi
if [[ $REPLY == 3 ]]; then
if [[ $(id -u) -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh $HOME
fi
sleep $DELAY
continue
fi
if [[ $REPLY == 0 ]]; then
break
fi
else
echo "Invalid entry."
sleep $DELAY
fi
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 ]; do
echo $count
count=$((count + 1))
done
echo "Finished."
使用循环读取文件
#!/bin/bash
# while-read: read lines from a file
while read distro version release; do
printf "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; do
printf "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 in
0) echo "Program terminated."
exit
;;
1) echo "Hostname: $HOSTNAME"
uptime
;;
2) df -h
;;
3) if [[ $(id -u) -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh $HOME
fi
;;
*) echo "Invalid entry" >&2
exit 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 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh $HOME
fi
;;
*) echo "Invalid entry" >&2
exit 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]; do
commands
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 ]]; do
if [[ -r $1 ]]; then
max_word=
max_len=0
for i in $(strings $1); do
len=$(echo $i | wc -c)
if (( len > max_len )); then
max_len=$len
max_word=$i
fi
done
echo "$1: '$max_word' ($max_len characters)"
fi
shift
done
在这个示例中,我们要在一个文件中查找最长的字符串。当在命令行中给出一个或多个文件名的时候, 该程序会使用 strings 程序(其包含在 GNU binutils 包中),为每一个文件产生一个可读的文本格式的 “words” 列表。 然后这个 for 循环依次处理每个单词,判断当前这个单词是否为目前为止找到的最长的一个。当循环结束的时候,显示出最长的单词。
如果省略掉 for 命令的可选项 words 部分,for 命令会默认处理位置参数()。
#!/bin/bash
for i; do
if [[ -r $i ]]; then
max_word=
max_len=0
for j in $(strings $i); do
len=$(echo $j | wc -c)
if (( len > max_len )); then
max_len=$len
max_word=$j
fi
done
echo "$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 )); do
commands
done
这里的 expression1、expression2和 expression3 都是算术表达式,
在行为方面,这相当于以下构造形式:
(( expression1 ))
while (( expression2 )); do
commands
(( expression3 ))
done
expression1 用来初始化循环条件,expression2 用来决定循环结束的时间,还有在每次循环迭代的末尾会执行 expression3。
#!/bin/bash
for (( i=0; i<5; i=i+1 )); do
echo $i
done
实例
report_home_space () {
if [[ $(id -u) -eq 0 ]]; then
cat <<- _EOF_
<H2>Home Space Utilization (All Users)</H2>
<PRE>$(du -sh /home/*)</PRE>
_EOF_
else
cat <<- _EOF_
<H2>Home Space Utilization ($USER)</H2>
<PRE>$(du -sh $HOME)</PRE>
_EOF_
fi
return
}
下一步,我们将重写它,以便提供每个用户家目录的更详尽信息,并且包含用户家目录中文件和目录的总个数:
report_home_space () {
local format="%8s%10s%10s\n"
local i dir_list total_files total_dirs total_size user_name
if [[ $(id -u) -eq 0 ]]; then
dir_list=/home/*
user_name="All Users"
else
dir_list=$HOME
user_name=$USER
fi
echo "<H2>Home Space Utilization ($user_name)</H2>"
for i in $dir_list; do
total_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_size
echo "</PRE>"
done
return
}
在 if 语句块内设置了一些随后会在 for 循环中用到的变量,来取代在 if 语句块内执行完备的动作集合。我们给 函数添加了几个本地变量,并且使用 printf 来格式化输出。
posted on 2025-10-12 20:14 chuchengzhi 阅读(8) 评论(0) 收藏 举报
浙公网安备 33010602011771号