Shell编程—结构化命令(2)

北慕城南 提交于 2019-12-15 20:28:02

1for命令

for命令的基本格式:

for var in list 
do
    commands 
done

在list参数中,你需要提供迭代中要用到的一系列值。

1.1读取列表中的值

例子:

$ vim test1
#!/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"
 
 执行结果:
$ ./test1
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

1.2读取列表中的复杂值

看这个例子:

$ vim badtest1
#!/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 "word:$test" 
 done 
 
 结果:
 $ ./badtest1 
 word:I 
 word:dont know if thisll 
 word:work

为什么会是这样呢?怎么处理?有两种办法:

  • 使用转义字符(反斜线)来将单引号转义;
  • 使用双引号来定义用到单引号的值。

处理之后的shell脚本:

$ vim test2
#!/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 "word:$test" 
 done
 
 结果:
 $ ./test2 
 word:I 
 word:don't 
 word:know 
 word:if 
 word:this'll 
 word:work

for循环每个值都是用空格分割的。如果有包含空格的数据值,也会导致这种类似的问题,所以处理方法可以采用上面的第二种(加引号)的方式。

1.3从变量读取列表

看例子:

$ vim test4
#!/bin/bash
# using a variable to hold the list
 list="Alabama Alaska Arizona Arkansas Colorado" 
 list=$list" Connecticut"
 for state in $list 
 do
      echo "Have you ever visited $state?" 
 done
 
 结果:
$ ./test4
Have you ever visited Alabama? 
Have you ever visited Alaska? 
Have you ever visited Arizona? 
Have you ever visited Arkansas?
Have you ever visited Colorado?
Have you ever visited Connecticut?

1.4从命令读取值

$ vim test5
#!/bin/bash
# reading values from a file
 file="states"
 for state in $(cat $file) 
 do
      echo "Visit beautiful $state" 
 done 
 
 $ cat states
Alabama 
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
执行结果:
$ ./test5
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
Visit beautiful Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
Visit beautiful Delaware 
Visit beautiful Florida 
Visit beautiful Georgia

1.5更改字段分隔符

IFS叫作内部字段分隔符

IFS环境变量定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将下列字符当作字段分隔符:

  • 空格
  • 制表符
  • 换行符

如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。

但是我们可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段分隔符的字符。例如,如果你想修改IFS的值,使其只能识别换行符,那就必须这么做:

IFS=$'\n'

将这个语句加入到脚本中,告诉bash shell在数据值中忽略空格和制表符。对前一个脚本使用这种方法,将获得如下输出。

$ vim test5b
#!/bin/bash
# reading values from a file
file="states" 
IFS=$'\n' 
for state in $(cat $file) 
do
     echo "Visit beautiful $state" 
done 

$ ./test5b
Visit beautiful Alabama 
Visit beautiful Alaska
Visit beautiful Arizona
Visit beautiful Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
Visit beautiful Delaware
Visit beautiful Florida
Visit beautiful Georgia
Visit beautiful New York
Visit beautiful New Hampshire 
Visit beautiful North Carolina

在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他地方继续沿用IFS的默认值。一个可参考的安全实践是在改变IFS之前保存原来的IFS值,之后再恢复它。  这种技术可以这样实现:

  IFS.OLD=$IFS   
IFS=$'\n'   
<在代码中使用新的IFS值>   
IFS=$IFS.OLD

 这就保证了在脚本的后续操作中使用的是IFS的默认值。

 

此外,我们还可以这样做,假如要做的是将IFS的值设为冒号:

IFS=:

如果要指定多个IFS字符,只要将它们在赋值行串起来就行。

IFS=$'\n':;"

这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。如何使用IFS字符解析数据没有任何限制。

 

1.6用通配符读取目录

$ vim test6 
#!/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 

$ ./test6 
/home/rich/test/dir1 is a directory 
/home/rich/test/myprog.c is a file 
/home/rich/test/myprog is a file 
/home/rich/test/myscript is a file 
/home/rich/test/newdir is a directory 
/home/rich/test/newfile is a file 
/home/rich/test/newfile2 is a file 
/home/rich/test/testdir is a directory 
/home/rich/test/testing is a file 
/home/rich/test/testprog is a file 
/home/rich/test/testprog.c is a file 

for命令会遍历/home/rich/test/*输出的结果。该代码用test命令测试了每个条目(使用方括号方法),以查看它是目录还是文件。for后面可以接多个需要循环遍历的路径。注意这里的判断条件“$file”用引号包围起来了,这是为了避免我们目录名和文件名有空格而导致误认为有多个文件而出错。

 

2 C语言风格的for命令

例一:使用单一变量

$ vim test8
#!/bin/bash
# testing the C-style for loop
 for (( i=1; i <= 10; i++ )) 
 do
      echo "The next number is $i" 
 done 
 
 $ ./test8
The next number is 1
The next number is 2
The next number is 3
The next number is 4
The next number is 5
The next number is 6
The next number is 7
The next number is 8
The next number is 9
The next number is 10

例二:使用多个变量

$ vim test9
#!/bin/bash
# multiple variables
 for (( a=1, b=10; a <= 10; a++, b-- )) 
 do 
     echo "$a - $b" 
 done 
 
 $ ./test9
1   - 10
2   - 9
3   - 8
4   - 7
5   - 6
6   - 5
7   - 4
8   - 3
9   - 2
10  - 1

 

3while命令

while命令的格式是:

while test command 
do
   other commands 
done

while命令的关键在于所指定的test command的退出状态码必须随着循环中运行的命令而改变。如果退出状态码不发生变化, while循环就将一直不停地进行下去。

$ vim test10
#!/bin/bash
# while command test
var1=5
while [ $var1 -gt 0 ] 
do
     echo $var1     
     var1=$[ $var1 - 1 ] 
done 

$ ./test10
5
4
3                                                        
2
1

使用多个测试命令:

$ vim test11
#!/bin/bash                                                  
# testing a multicommand while loop
var1=5                                                            
while echo $var1       
           [ $var1 -ge 0 ] 
do
    echo "This is inside the loop"     
    var1=$[ $var1 - 1 ] 
done 

$ ./test11
5
This is inside the loop 
4
This is inside the loop
3
This is inside the loop 
2
This is inside the loop 
1
This is inside the loop
0
This is inside the loop
-1

while语句中定义了两个测试命令。

while echo $var1  
                     [ $var1 -ge 0 ]

第一个测试简单地显示了var1变量的当前值。第二个测试用方括号来判断var1变量的值。在循环内部,echo语句会显示一条简单的消息,说明循环被执行了。注意当你运行本例时输出是如何结束的。

This is inside the loop -1

while循环会在var1变量等于0时执行echo语句,然后将var1变量的值减一。接下来再次执行测试命令,用于下一次迭代。echo测试命令被执行并显示了var变量的值(现在小于0了)。直到shell执行test测试命令,whle循环才会停止。

 

4until命令

until命令和while命令工作的方式完全相反。until命令要求你指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束了。

格式:

until test commands 
do
    other commands 
done

下面是使用until命令的一个例子。

$ vim test12
#!/bin/bash                                                   
# using the until command
var1=100                                                                 
until [ $var1 -eq 0 ] 
do
    echo $var1
    var1=$[ $var1 - 25 ] 
done 

$ ./test12
100
75                                                            
50
25

使用多个测试命令与while一样。

 

5嵌套循环

在for循环中嵌套for循环的简单例子:

$ vim test14
#!/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

$ ./test14
Starting loop 1:
    Inside loop: 1
    Inside loop: 2
    Inside loop: 3 
Starting loop 2:
    Inside loop: 1
    Inside loop: 2
    Inside loop: 3 
Starting loop 3:
    Inside loop: 1
    Inside loop: 2
    Inside loop: 3

在while循环内部放置一个for循环:

$ vim test15 
#!/bin/bash 
# placing a for loop inside a while loop 
var1=5  
while [ $var1 -ge 0 ] 
do
    echo "Outer loop: $var1"     
    for (( var2 = 1; $var2 < 3; var2++ ))    
    do 
       var3=$[ $var1 * $var2 ]       
        echo "  Inner loop: $var1 * $var2 = $var3"     
    done     
    var1=$[ $var1 - 1 ] 
done 

$ ./test15 
Outer loop: 5 
   Inner loop: 5 * 1 = 5 
   Inner loop: 5 * 2 = 10 
Outer loop: 4 
   Inner loop: 4 * 1 = 4 
   Inner loop: 4 * 2 = 8 
Outer loop: 3 
   Inner loop: 3 * 1 = 3 
   Inner loop: 3 * 2 = 6 
Outer loop: 2 
   Inner loop: 2 * 1 = 2 
   Inner loop: 2 * 2 = 4 
Outer loop: 1 
   Inner loop: 1 * 1 = 1 
   Inner loop: 1 * 2 = 2 
Outer loop: 0 
   Inner loop: 0 * 1 = 0 
   Inner loop: 0 * 2 = 0 

混用until和while循环:

$ vim test16 
#!/bin/bash 
# using until and while loops 
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 

$ ./test16 
Outer loop: 3 
    Inner loop: 3 / 1 = 3.0000 
    Inner loop: 3 / 2 = 1.5000 
    Inner loop: 3 / 3 = 1.0000     
    Inner loop: 3 / 4 = .7500 
Outer loop: 2     
    Inner loop: 2 / 1 = 2.0000 
    Inner loop: 2 / 2 = 1.0000 
    Inner loop: 2 / 3 = .6666 
    Inner loop: 2 / 4 = .5000 
Outer loop: 1 
    Inner loop: 1 / 1 = 1.0000 
    Inner loop: 1 / 2 = .5000 
    Inner loop: 1 / 3 = .3333 
    Inner loop: 1 / 4 = .2500 

 

6循环处理文件数据

通常必须遍历存储在文件中的数据。这要求结合已经讲过的两种技术:

  • 使用嵌套循环
  • 修改IFS环境变量

通过修改IFS环境变量,就能强制for命令将文件中的每行都当成单独的一个条目来处理,即便数据中有空格也是如此。一旦从文件中提取出了单独的行,可能需要再次利用循环来提取行中的数

典型的例子是处理/etc/passwd文件中的数据。这要求你逐行遍历/etc/passwd文件,并将IFS 变量的值改成冒号,这样就能分隔开每行中的各个数据段了:

#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS 
IFS=$'\n' 
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的行中解析出单独的值。

 

7控制循环

7.1break命令

1. 跳出单个循环

用for循环举例,当然break命令同样适用于while和until循环:

$ vim test17
#!/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"

$ ./test17
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
The for loop is completed

2. 跳出内部循环

在处理多个循环时,break命令会自动终止你所在的 内层的循环。

3. 跳出外部循环

有时你在内部循环,但需要停止外部循环。break命令接受单个命令行参数值:

break n

其中n指定了要跳出的循环层级。默认情况下,n为1,表明跳出的是当前的循环。如果你将n设为2,break命令就会停止下一级的外部循环。

$ vim test20
#!/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 

$ ./test20
Outer loop: 1
   Inner loop: 1
   Inner loop: 2
   Inner loop: 3
   Inner loop: 4

7.2continue命令

continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。可以在循环内部设置shell不执行命令的条件。

一个在for循环中使用continue命令的简单例子:

$ vim test21
#!/bin/bash
# using the continue command
 for (( var1 = 1; var1 < 9; var1++ )) 
 do 
    if [ $var1 -gt 3 ] && [ $var1 -lt 6 ]    
    then   
        continue    
    fi    
    echo "Iteration number: $var1" 
done 

$ ./test21
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 6
Iteration number: 7
Iteration number: 8

同样地,continue命令也有像break一样的另外两中用法,我们来看一下

continue n

 

8处理循环的输出

在shell脚本中,你可以对循环的输出使用管道或进行重定向。这可以通过在done命令之后添加一个处理命令来实现。

例一:

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

shell会将for命令的结果重定向到文件output.txt中,而不是显示在屏幕上。

例二:

$ vim test24
#!/bin/bash
 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"
 
$ ./test24
Alabama is the next place to go
Connecticut is the next place to go
Illinois is the next place to go
North Dakota is the next place to go
Tennessee is the next place to go
This completes our travels

state值并没有在for命令列表中以特定次序列出。for命令的输出传给了sort命令,该命令会改变for命令输出结果的顺序。运行这个脚本实际上说明了结果已经在脚本内部排好序了。

 

9实例

9.1查找可执行文件

当你从命令行中运行一个程序的时候,Linux系统会搜索一系列目录来查找对应的文件。这些目录被定义在环境变量PATH中。如果你想找出系统中有哪些可执行文件可供使用,只需要扫描PATH环境变量中所有的目录就行了。

$ vim test25
#!/bin/bash
# finding files in the PATH
 IFS=: 
 for folder in $PATH 
 do
     echo "$folder:"    
     for file in $folder/*    
     do   
         if [ -x $file ]       
         then     
              echo "   $file"       
          fi    
     done 
done

9.2创建多个用户账户

你不用为每个需要创建的新用户账户手动输入useradd命令,而是可以将需要添加的新用户账户放在一个文本文件中,然后创建一个简单的脚本进行处理。这个文本文件的格式如下:

userid,name

第一个条目是你为新用户账户所选用的用户ID。第二个条目是用户的全名。两个值之间使用逗号分隔,这样就形成了一种名为逗号分隔值的文件格式(或者是.csv)。

$ vim test26
#!/bin/bash
# process new user accounts
input="users.csv"
while IFS=',' read -r userid name 
do
  echo "adding $userid"   
  useradd -c "$name" -m $userid 
done < "$input"

运行这个脚本必须作为root用户,因为useradd命令需要root权限。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!