0%

Shell循环

The for Command

1
2
3
4
5
for var in word1 word2
do
command
command
done

shell 会进行文件替换。

1
2
3
4
for f in *
do
run $f
done

The $@ Variable

1
2
3
4
5
6
7
# args
echo Number of args passed is $#

for arg in $*
do
echo $arg
done
1
2
./args a b c
./args 'a b' c # 使用 $* 时,shell 会把 a b c 传给 for loop,导致执行三次

shell 使用 $1 $2 ... 替换 $*,而使用 $@ 时,shell 会使用 "$1" "$2" ... 替换。不同之处就是双引号。

1
2
3
4
5
echo Number of args passed is $#
for arg in "$@"
do
echo $arg
done

The for without the list

1
2
3
4
for arg 
do
echo $arg
done

如果省略 in list,那么 shell 会命令行的参数替换到这里,类似 for arg in "$@"

The while Command

1
2
3
4
while commandt
do
command
done
1
2
3
4
5
6
i=1
while [ "$i" -le 5 ]
do
echo $i
i=$((i + 1))
done

while 循环一般和 shift 配合使用来处理未知数量的命令行参数。

1
2
3
4
5
while [ "$#" -ne 0 ]
do
echo "$1"
shift
done

The until Command

while 命令是当判断为真是继续执行,而 until 则相反,如果条件为假则执行,为真不执行。

1
2
3
4
until [ command ]
do
command
done

默认情况下,当 log off 系统时,你启动的程序都会退出,如果想要程序继续执行则可以使用 nohup

More on loops

Breaking Out of a Loop

break 退出循环,break n,退出最近的 n 次循环。

Skipping the Remaining Commands in a Loop

continue continue n

Executing a Loop in the Background

可以使用 & 符号把整个循环放到后台执行。

1
2
3
4
$ for file in *
> do
> run $file
> done &

shell 把循环当作命令处理,所以可以在结构结束时(done、fi、esac)执行重定向、后台执行甚至使用管道。

I/O Redirection on a Loop

可以在循环上执行重定向操作,作用于循环体的输入重定向会应用到循环体中的所有命令,输出重定向也是如此,block redirection

1
2
3
4
$ for i in 1 2 3 4
> do
> echo $i # 每个命令都受到重定向的影响
> done > loopout

单个语句可以覆盖块重定向(block redirection),就像 shell 程序中的任何其他语句可以显式从指定源读取或将输出发送到指定目标一样。

要强制输入或输出来自或去往终端,使用 /dev/tty,它始终指你的终端程序,无论使用的是 Mac、Linux 还是 Unix 系统。

1
2
3
4
5
for file
do
echo "processing file $file" > /dev/tty
# other command
done > output

同样可以重定向循环的标准错误输出,在 done 后直接输入 2> file

1
2
3
4
while [ "$endofdata" -ne TRUE ]
do
:
done 2> errors # 标准错误输出重定向

循环体中所有命令的标准错误输出都被重定向到 errors 文件。

2> 格式的一种变体通常用于确保错误消息到达终端,即使脚本可能将其输出重定向到文件或管道:

echo "Error: no file" 1>&2,默认情况下,echo 将其输出发送到标准输出(文件描述符 1),而文件描述符 2 保留标准错误,默认情况下不会在文件重定向或管道上重定向。因此,上面的符号意味着来自 echo 的错误消息应该将其 file #1 输出重定向到file #2,即标准错误。

1
2
3
4
5
for i in "once"
do
echo "stdout msg"
echo "Error msg" 1>&2
done > /dev/null

即使整个循环体的标准输出都重定向了,里面的单个命令可以覆盖这种行为,第二个命令,把标准输出重定向到标准错误了。或者这样理解,标准输出被重定向了,但是第二个命令又把标准输出重定向到标准错误了。

Piping Data into and out of a Loop

一个命令的输出可以使用管道传递给一个循环体,而一个循环体的输出也可以通过管道传递给别的命令。

1
2
3
4
$ for i in 1 2 3 4
> do
> echo $i
> done | wc -l

Typing a Loop on One Line

1
2
3
4
for i in 1 2 3 4
do
echo $i
done

可以改写为一行 for i in 1 2 3 4; do echo $i; done

1
2
3
if [condition]; then
commad
fi

推荐这种形式写 shell。

The getopts Command

给 shell 脚本添加 options,当开始写这种程序时,会发现非常会变得特别复杂。

但是 shell 提供了内置的叫做 getopts 的命令,让命令行参数变得更加容易。

getopts options variableoptions 是一些字符,比如 ab:c,意味着 -a-c 是合法的选项,而 -b 则需要一个参数。

然而,getopts 命令被设计为在循环内执行,因为它可以轻松地执行每个用户指定选项所需的任何操作。每次循环时,getopts 都会检查下一个命令行参数,并通过检查该参数是否以减号开头并后跟指定为选项的任何字母来确定它是否是有效选项。如果是,getopts 将匹配的选项字母存储在指定变量内并返回零退出状态。

如果减号后面的字母未在选项中列出,则 getopts 在以零退出状态返回之前会在变量内存储一个问号。它还将有关用户指定的错误参数的错误消息写入标准错误。

如果命令行上没有留下更多参数,或者当前参数不以减号开头,则 getopts 返回非零退出状态,允许脚本处理任何后续参数。

例子

比如,想写一个脚本让它能识别 -a -i -r 选项,那么命令就像这样 getopts "air" option,这里第一个参数 air 指定了三个可接收的命令行标志(command flags) -a -i -roptiongetopts 将用来存储遇到的每个匹配值的变量的名称。

group/clustered options foo -a -i -r == foo -air

通过 : 告诉 getopts 某个选项需要参数。如果 getopts 在需要参数的选项后找不到参数,它会在变量中存储一个问号,并向标准错误输出一条错误消息。否则,它将变量中的字符和用户指定的参数存储在名为 OPTARG 的特殊变量中。

关于 getopts 的最后一点:另一个称为 OPTIND 的特殊变量最初设置为 1,并且每次 getopts 返回时都会更新以反映要处理的下一个命令行参数的编号。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#
# Wait until a specified user logs on
#

# setup default values
mailopt=FALSE
interval=60

while getopts mt: option
do
case "$option"
in
m ) mailopt=TRUE;;
t ) interval=$OPTARG;;
\? ) echo "Usage: waitfor [-m] [-t n] user"
echo " -m means to be informed by mail"
echo " -t means check every n secs"
exit 1;;
esac
done

if [ "$OPTIND" -gt "$#" ]
then
echo "Missing user name!"
exit 2
fi

shiftcount=$((OPTIND - 1))
shift $shiftcount
user=$1

until who | grep "^$user " > /dev/null
do
sleep $interval
done

if [ "$mailopt" = FALSE ]
then
echo "$user has logged on"
else
runner=$(who am i | cut -c1-8)
echo "$user has logged on | mail $runner"
fi