Linux(五) 更多结构化命令

关键词: 循环命令, for, while 和 until

  • for

  用于创建通过一系列值重复的循环. 每次重复使用系列中的一个值执行一个定义的命令集.

# 格式
for var in list
do
    commands
done

# list 提供一系列用于迭代的值, 指定列表中的值有几种不同的方法
# 每次迭代中, 变量var包含列表的当前值. 每一次迭代使用列表中的第一项,第二次迭代使用第二项,依次类推直到列表中的所有项都被使用为止
# 进入 do 和 done 语句之间的命令可以是一条或多条的标准 bash shell 命令.在命令中,变量 $var包含当前迭代的列表项值
# 注意: 如果愿意, 可以将 do 语句与 for 语句放在同一行,但是必须使用分号将它与列表项分开: for var in list; do.

  1)  读取列表中的值

#!/bin/bash
# basic for command

for test in Alabama Alaska Arizona Arkansas California Colorado
do
    echo The next state is $test
done

# 输出
# The next state is Alabama
# The next state is Alaska
# The next state is Arizona
# The next state is Arkansas
# The next state is California
# The next state is colorado

    每次 for 命令通过提供的值列表进行迭代时, 它将列表中的下一个值赋值给变量 test. 变量 $test 可以像在 for 命令语句中使用其他脚本变量一样使用. 最后一次迭代之后,变量 $test 在 shell 脚本的其他部分中仍然有效. 它仍然是迭代的最后一个值 ( 除非改变它的值 )

#!/bin/bash
# testing the for variable after the looping

for test in Alabama Alaska Arizona Arkansas California Colorado
do
    echo "The next state is $test"
done
echo "The last state we visited was $test"
test=Connecticut
echo "Wait, now we're visiting $test"

# 输出
# The next state is Alabama
# The next state is Alaska
# The next state is Arizona
# The next state is Arkansas
# The next state is California
# The next state is Colorado
# The last state we visited was Colorado
# Wait, now we're visiting Connecticut
# 变量 $test 保留它的值, 并且允许更改该值, 并在 for 命令循环之外像任何其他的变量一样使用它

  2) 读取列表中的复杂值

#!/bin/bash
# another example of how not to use the for command

for test in I don't know if this'll work
do
    echo "work: $test"
done

# 输出
# word: I ; word: dont know if  thisll ; word: work

# 问题
# shell 看到列表值当中的单引号, 并试图用它们来定义一个单独的数据值, 它的确破坏了这个过程.

# 解决
# 1. 使用转义字符 ( 反斜杠符号 ) 来转义单引号
# 2. 使用双引号来定义使用单引号的值

    解决方法:

#!/bin/bash
# another example of how not to use the for command
for tesst in I don't know if "this'll" work
do
    echo "word: $test"
done

# 输出
# word: I ; word: don't ; word: know ; word: if ; word: this'll ; word: word

# 在第一个问题值中,添加了反斜杠符号来转义 don't 值中的单引号
# 在第二个问题值中,使用了双引号把 this'll 包围起来.

# 问题
# 可能遇到的另一个问题是多行字值. for循环认为每个值都用空格分隔, 如果有包含空格的数据值就会遇到另一个问题

# 解决
# 如果在个别的数据值中有空格, 必须使用双引号将它们包围起来

    包含空格的数据值处理:

#!/bin/bash
# another example off how not to use the for command

for test in Nevada New Hampshire New Mexico New York North Carolina
do
    echo "Now going to $test"
done

# 输出
# Now going to Nevada  ; Now going to New  ; Now going to Hampshire  ; Now going to New  ; Now going to Mexico  ; Now going to New  ; Now going to York  ; Now going to North  ; Now going to Carolina  ;


# 加双引号
for test in Nevada "New Hampshire" "New Mexico" "New York"
do
  echo "Now going to $test"
done

# 输出:
#
Now going to Nevada ; Now going to New Hampshire ; Now going to New Mexico ; Now going to New York ;

# 使用双引号包围值时, shell 不会将双引号作为值的一部分

  3) 从变量读取列表

    shell 脚本中经常发生的是基类了存储于变量中的一个值列表, 然后需要通过列表迭代. 也可以使用 for 命令做到这一点

#!/bin/bash
# using a variable to hold the list

list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"

for state inn $list
do 
    echo "Have you ever visited $state?"
done

# 变量 $list 包含用于迭代的标准的文本值列表. 
# 注意,代码可以使用另一种赋值语句向包含在 $list 变量中的已存在列表添加 ( 或连接 ) 一条项目.
# 这是一种将文本添加到一个已存在的、存储在一个变量中的文本字符串末尾的常用方法

  4) 读取命令中的值

    生成列表中使用的值的另一种方法是使用命令的输出. 可以使用反引号字符来执行生成输出的任何命令, 然后在 for 命令中使用命令的输出:

#!/bin/bash
# reading values from a file

file="states"
for state in `cat $file`
do
    echo "Visit beautiful $state"
done

# 使用 cat 命令显示文件 states 的内容.如果列出的行中包含一个空格, for命令仍然将每个单词作为一个单独的值
# 注意: 附件名赋值给一个变量,只使用了文件名而没有使用路径,这就需要文件和脚本在同一路径下, 如果不是这样就需要使用完整的路径来引用文件位置

  5) 改变字段分隔符

    引起这个问题的原因是称为内部字段分隔符(internal field separator, IFS)的特殊环境变量. 环境变量IFS定义了 bash shell 用作字段分隔符的字符列表. 默认情况下, bash shell 将下面的字符看作字段分隔符: 空格 制表符 换行符. 如果 bash shell 在数据中遇到这几种字符中的一种, 它就会认为您正在列表中启动新的数据字段. 当处理能够包含空格的数据(如文件名)时, 就会产生干扰.

    要解决该问题, 可以在 shell 脚本中暂时更改环境变量 IFS 的值, 限制bash shell 看作是字段分隔符的字符.然而这样做有点奇怪. 例如, 如果想将 IFS 的值更改为只识别换行符, 需要这样做: IFS=$' '. 在脚本找那个添加这条语句, 通知 bash shell 在数据值中忽略空格和制表符.

#!/bin/bash
# reading values from a file

file="states"

IFS=$'
'
for state in `cat $file`
do
    echo "Visit beautiful $state"
done

# 现在 shell 脚本可以在列表中使用包含空格的值了
# 警告: 当使用较长的脚本时, 可能在一个位置更改了 IFS 的值,但是忘记了该值, 并在脚本的其他地方假设为默认值
# 一个安全的做法是, 在更改原始 IFS 值之前将它保存起来, 当需要运用即可恢复
IFS.OLD=$IFS
IFS=$' '
<use the new IFS value in code>
IFS=$IFS.OLD

# 环境变量 IFS 有其他的很好的应用. 如果想迭代文件中用冒号分隔的值(如在文件/etc/passwd中). 需要做的就是将IFS的值设置为冒号
IFS=:
# 如果想指定多个 IFS 字符, 只需将它们在赋值行中串联起来即可
IFS=$' ':;"
# 该赋值使用了换行符、冒号、分号和双引号字符作为字段分隔符. 对于使用的 IFS 字符如何解析数据没有限制

  6) 使用通配符读取目录

    可以使用 for 命令自动迭代文件的目录. 为此, 必须在文件或路径名中使用通配符. 这就迫使 shell 使用文件统通配(file globbing). 文件通配是生成与指定通配符匹配的文件或路径名的过程.

#!/bin/bash
# iterate through all the files in a directory

for file in /home/rich/test/*
do
    if [ -d "$file" ]
    then
        echo "$file is a directory"
    elif [ -f "$file" ]
    then
        echo "$file is a file"
    fi
done

    for 命令迭代 /home/rich/test/* 列表的结果. 代码使用 test 命令(使用方括号方法)测试每个项目, 查看它是目录 (使用 -d 参数) 还是文件 (使用 -f 参数)

    在 Linux 中, 包含空格的路径和文件名是合法的. 要容纳它们应该使用双引号将变量 $file 包围起来.如果不这样做,遇到包含空格的路径或文件名时就会出错:

    

     bash shell 将 test 命令中的其他词语理解为参数, 这会生成错误. 通过在 for 命令中列出一系列目录通配符, 可以在同一个 for 语句中将目录搜索方法和列表方法结合起来

#!/bin/bash
# iterating through multiple directories

for file in /home/rich/.b* /home/rich/badtest
do
    if [ -d "$file" ]
    then
        echo "$file is a directory"
    elif [ -f "$file" ]
    then
        echo "$file is a file"
    else
        echo "$file doesn't exist"
    fi
done

    for 语句首先使用文件通配迭代通配符生成的文件列表, 然后迭代列表中的下一个文件. 可以结合列表中任意数目的通配符项进行迭代. 

    警告: 可以在列表数据中输入任何值, 即使文件或目录不存在, for 语句会试图处理放在列表中的任何东西. 但遇到文件和目录时, 这可能是一个问题.无法知道是否在迭代一个不存在的目录: 在试图处理文件或目录之前检查一个通常是个好方法.

  • C 式的 for 命令

   1) C语言中的 for 命令

    C 语言中的 for 命令有一种特定的方法指定一个变量, 即必须保持 true 值用于继续迭代的条件, 和一种每次迭代改变变量的方法. 当特定的条件变为false, for 循环结束. 条件式使用标准的数学符号定义.

for (i = 0; i < 10; i++)
{
  printf("The next number is %d
", i);            
}

    该代码生成一个简单迭代循环, 变量 i 用作计数器. 第一部分 将默认值赋值给变量. 中间部分定义循环迭代的条件. 当定义的条件变为 false 时, for 循环停止迭代. 最后一部分定义迭代过程. 每次迭代之后, 将执行在最后一部分中定义的表达式. 在本例中, 变量 i 每次迭代之后都增加 1.

    bash shell 也支持看起来与 C 式的 for 循环类似的 for 循环版本, 尽管二者有细微的差别, 其中几点差别连程序员也很混淆. 这是 bash 中 C 式的 for 循环的基本格式: for (( variable assignment ; condition ; iteration process ))

    对于 bash shell 脚本程序员来说, C 式 for循环的格式能令人混淆, 因为它使用 C 式的变量引用而不是 shell 式的变量引用. 这里是 C 式的 for 方法: for (( a=1 ; a<10 ; a++ ))

    注意, 有几项不遵循标准 bash shell 的 for 方法: 变量的赋值可以包含空格 , 条件中的变量不以美元符号做前缀 , 迭代处理式不使用 expr 命令格式

#!/bin/bash
# testing the C-style for loop

for (( i=1; i<=10; i++ ))
do
    echo "The next number is $i"
done

    for 循环使用在 for 循环中定义的变量迭代命令 (在本实示例中是字母 i ). 在每次迭代中, 变量 $i 都包含 for 循环中分配的值. 每次迭代之后, 循环迭代过程就会应用到变量, 在本示例中, 变量增加1

  2) 使用多个变量

    C 式的 for 命令也允许使用多个变量迭代. 循环分别处理每个变量, 允许为每个变量定义不同的迭代过程.虽然可以使用多个变量, 但只可以在 for 循环中定义一个条件

#!/bin/bash
# multiple variables

for (( a=1, b=10; a<=10; a++, b-- ))
do
    echo "$a - $b"
done
  • while

  while 命令有点像 if-then 语句和 for 循环建的结合. while 命令允许定义要测试的命令, 然后只要定义的测试命令返回0退出状态代码, 就魂环一组命令. 它在每次迭代开始时检查测试命令. 测试命令返回非零退出状态代码时, while 命令停止执行命令集.

  1) while 的基本格式

while test command
do
    other commands
done

    在 while 命令中定义的 test 命令与在 if-then 语句中定义的格式一样. 就像在 if-then 语句中一样, 可以使用任何常规的 shell 命令, 或者可以使用 test 命令检测条件(如变量值).

    while 命令的关键是指定的test命令的退出状态必须根据循环中命令的运行情况改变. 如果退出状态不改变, while 循环就会陷入无限的循环中. 最常见的情况是使用 test 命令括号来检验用于循环命令的 shell 变量的值

#!/bin/bash
# while command test

var1=10
while [ $var1 -gt 0 ]
do
    echo $var1
    var1=$[ $var1 -1 ]
done

# 输出
# 10 ; 9 ; 8 ; 7 ; 6 ; 5 ; 4 ; 3 ; 2 ; 1

    while 命令定义测试条件来校验每次迭代: while [ $var1 -gt 0]. 只要测试条件为 truw, while 命令hi继续循环定义的命令. 在这些命令中, 用于检测条件的变量一定要改变, 否则就会无穷循环. 该例中使用shell算术将变量值减1: var1=$[ $var1 -1 ]. 测试条件不再为 true 时, while 循环停止.

  2) 使用多条测试命令

    while 命令允许在 while 语句行定义多条 test 命令. 只有最后一条测试命令的退出状态时用来决定循环是何时停止的.

#!/bin/bash
# testing a multicommand while loop

var1=10

while echo $var1 
[ $var1 -ge 0 ] do var1=$[ $var1 - 1 ] done

# 输出:
# 10 ; 9 ; 8 ; 7 ; 6 ; 5 ; 4 ; 3 ; 2 ; 1 ; 0 ; -1

# 第一条测试命令简单地显示了变量 var1 的当前值. 第二条命令使用屙屎命令决定变量 var1 的值.
# 当变量 var1 的值等于零时, while 循环被执行. 接下来, 用于下一次迭代的测试条件被执行. 执行 echo 测试命令, 显示变量值小于零
# 直到 shell 执行 test 测试命令, while 循环才终止
# 这表明,在多命令 while 语句中,所有的测试命令在每次迭代中都执行,包含测试命令失败的最后一次迭代
  • until

  until 命令刚好与 while 命令相反. until 命令需要制定一条测试命令, 这条命令通常产生一个非零的退出状态. 只要测试命令的退出状态非零, bash shell 就会执行列在循环当中的命令. 一旦测试条件返回零退出状态,循环停止.

until test commands
do
    other commands
done

  与 while 命令相似, 可以在 until 命令语句中使用多条测试命令. 只有最后一条命令的退出状态能够决定 bash shell 是否执行其他定义的命令.

#!/bin/bash
# using the until command

var1=100

until [ $var1 -eq 0 ]
do
    echo $var1
    var1=$[ $var1 -25 ]
done

# 输出
# 100 ; 75 ; 50 ; 25

  这个例子测试了变量 var1 以决定 until 循环何时停止. 一旦变量的值等于0, until 命令就停止循环. 在 until 命令中使用多条测试命令时, 要注意的问题与使用 while 命令要注意的问题相同

#!/bin/bash
# using the until ommand

var1=100

until echo $var1
        [ $var1 -eq 0 ]
do
    echo Insiide the loop: $var1
    var1=$[ $var1 - 25 ]
done
  • 嵌套循环

  一条循环语句可以在循环中使用任何类型的命令, 包括其他循环命令. 这称为嵌套循环. 使用嵌套循环时要谨慎, 因为是在一个迭代内部执行另一个迭代, 它增加了正在运行的命令的运行次数

#!/bin/bash
# nesting for loops

for (( a = 1; a <= 3; a++ ))
do
    echo "Starting loop $a:"
    for (( b = 1; b <= 3; b++ ))
    do
        echo " Inside loop: $b"
    done
done

  嵌套循环 (也称为内循环) 对每次外部煦暖迭代都迭代所有的值. 注意, 两个循环的 do 命令和 done 命令之间没区别. bash shell 知道, 当第一条 done 命令执行时, 它指的是内循环而不是外循环.

  • 文件数据的循环

  通常需要迭代存储在文件内部的项. 这就需要结合两种介绍过的技术: 使用嵌套循环 , 更改环境变量 IFS

  通过更改环境变量 IFS, 可以迫使 for 命令将文件中的 每行作为单独的一项来处理, 即使数据包含空格. 提取了文件中的个别行之后, 还可以再循环以提取其包含的数据.

  这个问题的经典实例是在 /etc/passwd 文件中处理数据. 这就需要逐行迭代文件 /etc/passwd,, 然后将变量 IFS 的值更改为冒号, 以便能将每行中的单个组成部分分离出来.

#!/bin/bash
# changing the IFS value

IFS.OLD=$IFS
IFS=$'
'

for entry in `cat /etc/passwd`
do
    echo "Values in $entry -"
    IFS=:
    for value in $entry
    do
        echo " $value"
    done
done

  该脚本使用两个不同的 IFS 值解析数据. 第一个 IFS 值解析文件 /etc/passwd 中的单独行. 接下来的内部 for 循环将 IFS 值更改为冒号, 它允许解析文件 /etc/passwd 行中的单独值. 内部循环解析 /etc/passwd 项的每个单独值. 这也是处理用逗号分隔数据的一种好方法, 也是淡入电子表数据的一种常用方法

  • 控制循环 ( break  continue)

  1) break

    break 命令是在处理过程中跳出循环的一种简单方法. 可以使用 break 命令退出任何类型的循环, 包括 while 循环和 until 循环

    1.1 跳出单循环

#!/bin/bash
# breaking out of a for loop

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"

# for 循环通常迭代列表中指定的所有值. 然而, 当 if-then 条件满足时, shell 执行break 命令, 终止 for 循环

    1.2 跳出内循环

#!/bin/bash
# breaking out of an inner loop

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

# 内循环的 for 语句规定迭代到变量 b 的值等于 100. 然而, 内循环的 if-then 语句指定变量的值等于5时,
# 执行 break 命令. 注意, 即使内循环被 break 命令终止, 外循环会继续按指定进行

    1.3 跳出外循环

      可能有时处于内循环但需要停止外循环. break 命令包括单独的命令行参数值: break n

      n 表明要跳出的循环级别. 默认情况下, n是1, 代表跳出当前循环. 如果将 n 设置为2, break 命令将停止外循环的下一级循环

#!/bin/bash
# breaking out of an outer loop

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

  2) continue 命令

  continue 命令是一种提前停止循环的内命令, 而不完全终止循环的方法. 这就允许在循环中设置 shell 不执行命令的条件. 

#!/bin/bash
# using the continue command

for (( var1 = 1; var1 < 15; var1++ ))
do
    if [ $var1 -gt 5 ]  && [ $var1 -lt 10 ]
     then
        continue
    fi
    echo "Iteration number: $var1"
done    

# 输出:
# 1 ; 2 ; 3 ; 4 ; 5 ; 10 ; 11 ; 12 ; 13 ; 14

  if-then 语句的条件 (值大于5小于10) 满足时, shell执行 continue 命令, 跳过该循环中余下的命令, 但是循环继续进行. 如果 if-then 语句的条件不再满足, 那么循环就恢复正常.

  在 while 循环和 until 循环中可以使用  continue 命令, 但是要特别谨慎. 当 shell 执行 continue 命令时, 它跳过余下的命令. 如果您正在其中的一个条件中增加测试条件的变量, 循环有可能不会终止

#!/bin/bash
# improperly using the continue command in a while loop

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

# 哟啊确保将该脚本的输出重定向给 more 命令, 这样才能够分屏查看. 一起都运行正常, 直到 if-then 条件满足, shell执行continue命令
# 当 shell 执行 continue 命令时, 它跳过 while 循环中增加计算器变量的地方, 当前循环就进入了死循环

  与使用 break 命令一样,  continue 命令也允许使用命令行参数指定要继续的循环级别: continue n

#!/bin/bash
# continuing an outer loop

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

  适应 continue 命令停止处理循环内部的命令, 但是继续外部循环. 注意在脚本输出中, 迭代的值为 3 时不处理任何内循环语句, 因为 continue 命令停止了处理, 但是继续外循环处理

  • 处理循环的输出

  可在 shell 脚本中使用管道或者重定向循环输出结果. 可以通过在 done 命令的末尾添加处理命令来做到这一点

#!/bin/bash

for file in /home/test_sh/*
do
    if [ -d "$file" ]
    then
        echo "$file is a directory"
    else
        echo "$file is a file"
    fi
done > output.txt

# shell 将 for 命令的结果重定向到文件 output.txt, 而不是显示在监视器上

   下面是一个将 for 命令的输出结果重定向到文件的例子:

#!/bin/bash
# redirecting the for output to a file

for (( a = 1; a < 10; a++ ))
do
    echo "The number is $a"
done > test23.txt
echo "The command is finished. "

# shell 创建了文件 test23.txt, 只将 for 命令的输出重定向到文件. 但是, shell 正常显示了 for 命令之后 echo语句

  同样的技术也可用于将循环的输出管道传送给其他命令

#!/bin/bash
# piping a loop to another command

for state in "North Dakota" Connecticut Illinois Alabama Tennessee
do
    echo "$state is the next place to go"
done | sort
echo "This completes our travels"

# state 值没有在 for 命令列表中任何特定的顺序中列出. for 命令的输出结果被管道传送给sort 命令, 它会改变 for 命令输出的顺序. 运行脚本确实显示输出在脚本中进行了恰当的排序

小结:

  循环是编程不可缺少的一部分. bash shell 提供了3中不同的循环命令, 可以用在脚本中. for 命令能够迭代值列表, 无论是命令行内部提供的还是包含在变量中的, 或者使用文件通配从通配符中提取文件和目录的名称.

  while 命令提供一种基于命令的条件进行循环的一种方法, 可以使用普通命令或测试命令来测试变量条件. 只要命令 (或条件) 生成零退出状态, while 循环将继续重复指定的命令集.

  until 命令也提供一种重复命令的方法, 但是它是基于一条命令 (或者条件) 生成非零退出状态, 这个特性允许我们在重复停止之前设定一个必须满足的条件.

  可以再 shell 脚本中结合循环生成多层循环. bash shell 提供continue 命令和 break 命令, 允许我们根据循环内的不同值改变征程的循环处理流.

  bash shell 也允许我们使用标砖命令重定向到管道传送更改循环的输出. 可以使用重定向将循环的输出重定向到文件, 或者将循环的输出管道传送给另一条命令. 这个提供了很多特性, 可以使用这些特性控制 shell 脚本的执行.

原文地址:https://www.cnblogs.com/lab-zj/p/12982637.html