for 命令
bash shell提供了for命令,允许你创建一个遍历一系列值的循环。每次迭代都使用其中一个
值来执行已定义好的一组命令。下面是bash shell中for命令的基本格式。
for var in list
do
commands
done
在list参数中,你需要提供迭代中要用到的一系列值。可以通过几种不同的方法指定列表
中的值。在每次迭代中,变量var会包含列表中的当前值。第一次迭代会使用列表中的第一个值,第
二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。
有几种不同的方式来指定列表中的值
# 1. 读取列表中的值
# $test 会一直保持最后一次迭代的值
for test in Alabama Alaska Arizona Arkansas California Colorado; do
echo The next state is $test
done
echo "last value: $test"
# 2. 读取列表中的复杂值
# 'xxx xxx xxx' 被视为一整块, 和 don ll 连成了一个整体被作为列表的一项
for test in I don't know if this'll work; do
echo "word:$test"
done
# 有两种办法可解决这个问题:
# * 使用转义字符(反斜线)来将单引号转义;
# * 使用双引号来定义用到单引号的值。
for test in I don\'t know if "this'll" work; do
echo "word:$test"
done
# for循环假定每个值都是用空格分割的。如果有包含空格的数据值就必须用双引号将这些值圈起来
for test in Nevada "New Hampshire" "New Mexico" "New York"; do
echo "Now going to $test"
done
# 3. 从变量读取列表
# 通常shell脚本遇到的情况是,你将一系列值都集中存储在了一个变量中,然后需要遍历变量
# 中的整个列表。也可以通过for命令完成这个任务.
# 注意,代码还用了另一个赋值语句向$list 变量包含的已有列表中添加(或者说是拼接)了一个值。
# 这是向变量中存储的已有文本字符串尾部添加文本的一个常用方法。
list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"
for state in $list; do
echo "Have you ever visited $state?"
done
# 4. 从命令读取值
# 生成列表中所需值的另外一个途径就是使用命令的输出。可以用命令替换来执行任何能产生
# 输出的命令,然后在for命令中使用该命令的输出。
file="states"
for state in $(cat $file); do
echo "Visit beautiful $state"
done
# 5. 更改字段分隔符
# IFS 环境变量定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将下列字
# 符当作字段分隔符:
# * 空格
# * 制表符
# * 换行符
# 如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数
# 据字段的开始。在处理可能含有空格的数据(比如文件名)时,这会非常麻烦
# 要解决这个问题,可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段
# 分隔符的字符。例如,如果你想修改IFS的值,使其只能识别换行符,那就必须这么做:
# IFS=$'\n'
file="states"
IFS=$'\n'
for state in $(cat $file); do
echo "Visit beautiful $state"
done
# 常见的一种情况是可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他地方
# 继续沿用IFS的默认值。一个可参考的安全实践是在改变IFS之前保存原来的IFS值,之后再恢复它
IFS.OLD=$IFS
IFS=$'\n'
<在代码中使用新的IFS值>
IFS=$IFS.OLD
# 如果要指定多个IFS字符,只要将它们在赋值行串起来就行, 下例将换行符、冒号、分号和双引号作为字段分隔符
# IFS=$'\n':;"
# 6. 用通配符读取目录
# 可以用for命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中
# 使用通配符。它会强制shell使用文件扩展匹配。文件扩展匹配是生成匹配指定通配符的文件名或
# 路径名的过程。如果不知道所有的文件名,这个特性在处理目录中的文件时就非常好用。
# 注意: 也可以在for命令中列出多个目录通配符,将目录查找和列表合并进同一个for语句
# 注意: 在Linux中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将$file变
# 量用双引号圈起来。如果不这么做,遇到含有空格的目录名或文件名时就会有错误产生。
for file in /home/shino/* /home/shino/test; do
if [ -d "$file" ]; then
echo "$file is a directory"
elif [ -f "$file" ]; then
echo "$file is a file"
fi
done
# 注意,你可以在数据列表中放入任何东西。即使文件或目录不存在,for语句也会尝试处
# 理列表中的内容。在处理文件或目录时,这可能会是个问题。你无法知道你正在尝试遍
# 历的目录是否存在:在处理之前测试一下文件或目录总是好的
C 语言风格的 for 命令
基本格式
for (( variable assignment ; condition ; iteration process ))
C 语言风格的for命令看起来如下
for (( a = 1; a < 10; a++ ))
注意,有些部分并没有遵循bash shell标准的for命令
- 变量赋值可以有空格;
- 条件中的变量不以美元符开头
- 迭代过程的算式未用expr命令格式
# 使用多个变量
for (( a=1, b=10; a <= 10; a++, b-- )); do
echo "$a - $b"
done
while 命令
while命令某种意义上是if-then语句和for循环的混杂体。while命令允许定义一个要测试
的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。它会在每次迭代的
一开始测试test命令。在test命令返回非零退出状态码时,while命令会停止执行那组命令
while test command
do
other commands
done
while命令中定义的test command和if-then语句中的格式一模一样。可
以使用任何普通的bash shell命令,或者用test命令进行条件测试,比如测试变量值。
while命令的关键在于所指定的test command的退出状态码必须随着循环中运行的命令而
改变。如果退出状态码不发生变化, while循环就将一直不停地进行下去。
# 1. 最常见的test command的用法是用方括号来检查循环命令中用到的shell变量的值
var1=10
while [ $var1 -gt 0 ]; do
echo $var1
var1=$[ $var1 - 1 ]
done
# 2. 使用多个测试命令
# 和 if-then 一样, while命令允许你在 while 语句行定义多个测试命令。只有最后一个测试命令的退出状态码
# 会被用来决定什么时候结束循环。比如下面 var 为 0 时还执行最后的 echo $var1 才退出
var1=10
while echo $var1; [ $var1 -ge 0 ]; do
echo "This is inside the loop"
var1=$[ $var1 - 1 ]
done
3. until 命令
until命令和while命令工作的方式完全相反。until命令要求你指定一个通常返回非零退
出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。
一旦测试命令返回了退出状态码0,循环就结束了。和你想的一样,until命令的格式如下
until test commands
do
other commands
done
举一个简单的粒子
var1=100
until [ $var1 -eq 0 ]; do
echo $var1
var1=$[ $var1 - 25 ]
done
嵌套循环
循环语句可以在循环内使用任意类型的命令,包括其他循环命令。这种循环叫作嵌套循环(nested loop)
# 注意,在使用嵌套循环时,你是在迭代中使用迭代,与命令运行的次数是乘积关系。不注意这点的话,有可能会在脚本中造成问题
for (( a = 1; a <= 3; a++ )); do
echo "Starting loop $a:"
for (( b = 1; b <= 3; b++ )); do
echo " Inside loop: $b"
done
done
# 混用循环命令时也一样
var1=9
while [ $var1 -ge 0 ]; do
echo "Outer loop: $var1"
for (( var2 = 1; $var2 < 9; var2++ )); do
var3=$[ $var1 * $var2 ]
echo " Inner loop: $var1 * $var2 = $var3"
done
var1=$[ $var1 - 1 ]
done
# 如果真的想挑战脑力,可以混用until和while循环
var1=3
until [ $var1 -eq 0 ]; do
echo "Outer loop: $var1"
var2=1
while [ $var2 -lt 5 ] do
var3=$(echo "scale=4; $var1 / $var2" | bc)
echo " Inner loop: $var1 / $var2 = $var3"
var2=$[ $var2 + 1 ]
done
var1=$[ $var1 - 1 ]
done
循环处理文件数据
通常必须遍历存储在文件中的数据。这要求结合已经讲过的两种技术:
- 使用嵌套循环
- 修改IFS环境变量
通过修改IFS环境变量,就能强制for命令将文件中的每行都当成单独的一个条目来处理,
即便数据中有空格也是如此。一旦从文件中提取出了单独的行,可能需要再次利用循环来提取行中的数据。
典型的例子是处理/etc/passwd文件中的数据。这要求你逐行遍历/etc/passwd文件,并将IFS
变量的值改成冒号,这样就能分隔开每行中的各个数据段了。
IFS=$'\n'
for line in $(cat /etc/passwd); do
IFS=:
echo -n "line: "
for item in $line; do
echo -n "$item "
done
echo ""
done
控制循环
- break命令
- continue命令
# 1. 跳出单个循环
for var1 in 1 2 3 4 5 6 7 8 9 10; do
if [ $var1 -eq 5 ]; then
break
fi
echo "Iteration number: $var1"
done
echo "The for loop is completed"
# 2. 跳出内部循环
# 在处理多个循环时,break命令会自动终止你所在的最内层的循环
for (( a = 1; a < 4; a++ )); do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ )); do
if [ $b -eq 5 ]; then
break
fi
echo " Inner loop: $b"
done
done
# 3. 跳出外部循环
# 有时你在内部循环,但需要停止外部循环。break命令接受单个命令行参数值:
# break n
# 其中n指定了要跳出的循环层级。默认情况下,n为1,表明跳出的是当前的循环。如果你将
# n设为2,break命令就会停止下一级的外部循环
for (( a = 1; a < 4; a++ )); do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ )); do
if [ $b -gt 4 ]; then
break 2
fi
echo " Inner loop: $b"
done
done
# 4. continue 命令
# continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。可以在循环
# 内部设置shell不执行命令的条件。这里有个在for循环中使用continue命令的简单例子。
for (( var1 = 1; var1 < 15; var1++ )); do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]; then
continue
fi
echo "Iteration number: $var1"
done
# 也可以在while和until循环中使用continue命令,但要特别小心。记住,当shell执行
# continue命令时,它会跳过剩余的命令。如果你在其中某个条件里对测试条件变量进行增值, 问题就会出现
# 例如下例, 当 var1 变化到 6 时, continue 导致后面的累加条件不再得到执行, 从而进入死循环
# 你得确保将脚本的输出重定向到了more命令,这样才能停止输出
var1=0
while echo "while iteration: $var1"; [ $var1 -lt 15 ]; do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]; then
continue
fi
echo " Inside iteration number: $var1"
var1=$[ $var1 + 1 ]
done
# 5. continue 继续外循坏
# 和break命令一样,continue命令也允许通过命令行参数指定要继续执行哪一级循环:
# continue n
# 其中n定义了要继续的循环层级。下面是继续外部for循环的一个例子
# 因为使用 C 风格 for, continue后, a++ 也会被执行, 从而不会导致 a 卡死在 3 的情况
for (( a = 1; a <= 5; a++ )); do
echo "Iteration $a:"
for (( b = 1; b < 3; b++ )); do
if [ $a -gt 2 ] && [ $a -lt 4 ]; then
continue 2
fi
var3=$[ $a * $b ]
echo " The result of $a * $b is $var3"
done
done
# 6. 处理循环的输出
# 你可以对循环的输出使用管道或进行重定向。这可以通过在done命令之后添加一个处理命令来实现。
for state in "North Dakota" Connecticut Illinois Alabama Tennessee; do
echo "$state is the next place to go"
done | sort > output.txt
实例
- 查找可执行文件
当你从命令行中运行一个程序的时候,Linux系统会搜索一系列目录来查找对应的文件。这
些目录被定义在环境变量PATH中。如果你想找出系统中有哪些可执行文件可供使用,只需要扫
描PATH环境变量中所有的目录就行了。如果要徒手查找的话,就得花点时间了。不过我们可以
编写一个小小的脚本,轻而易举地搞定这件事
IFS=:
for folder in $PATH; do
for file in $folder/*; do
if [ -x "$file" ]; then
echo "$file"
fi
done
done
- 创建多个用户账户
shell脚本的目标是让系统管理员过得更轻松。如果你碰巧工作在一个拥有大量用户的环境
中,最烦人的工作之一就是创建新用户账户。好在可以使用while循环来降低工作的难度。
你不用为每个需要创建的新用户账户手动输入useradd命令,而是可以将需要添加的新用户
账户放在一个文本文件中,然后创建一个简单的脚本进行处理。这个文本文件的格式如下:
userid,username
第一个条目是你为新用户账户所选用的用户ID。第二个条目是用户的全名。两个值之间使用
逗号分隔,这样就形成了一种名为逗号分隔值的文件格式(或者是.csv)。这种文件格式在电子表
格中极其常见,所以你可以轻松地在电子表格程序中创建用户账户列表,然后将其保存成.csv格
式,以备shell脚本读取及处理
要读取文件中的数据,得用上一点shell脚本编程技巧。我们将IFS分隔符设置成逗号,并将
其放入while语句的条件测试部分。然后使用read命令读取文件中的各行。实现代码如下
read命令会自动读取.csv文本文件的下一行内容,所以不需要专门再写一个循环来处理。当
read命令返回FALSE时(也就是读取完整个文件时),while命令就会退出。妙极了!
input="user.csv"
while IFS=',' read -r userid name; do
echo "adding: userid $userid name $name"
useradd -c "$name" -m $userid
done < "$input"
网友评论