shell 入门 03 循坏

2022-09-20  本文已影响0人  wjundong

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命令

# 使用多个变量
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环境变量,就能强制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

控制循环

# 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

实例

IFS=:
for folder in $PATH; do
    for file in $folder/*; do
        if [ -x "$file" ]; then
            echo "$file"
        fi
    done
done
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"
上一篇 下一篇

猜你喜欢

热点阅读