zoukankan      html  css  js  c++  java
  • Linux Shell 之 for 循环语句

    1、for 命令

      重复执行一系列命令在编程中很常见。通常你需要重复一组命令直至达到某个特定条件,比如处理某个目录下的所有文件、系统上的所有用户或是某个文本文件中的所有行。
      bash shell提供了for命令,允许你创建一个遍历一系列值的循环。每次迭代都使用其中一个值来执行已定义好的一组命令。下面是bash shell中for命令的基本格式。

    1 for var in list
    2 do
    3   commands
    4 done

      在list参数中,你需要提供迭代中要用到的一系列值。可以通过几种不同的方法指定列表中的值。
      在每次迭代中,变量var会包含列表中的当前值。第一次迭代会使用列表中的第一个值,第二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。
      在do和done语句之间输入的命令可以是一条或多条标准的bash shell命令。在这些命令中,$var变量包含着这次迭代对应的当前列表项中的值。

    说明 只要你愿意,也可以将do语句和for语句放在同一行,但必须用分号将其同列表中的值分开:for var in list; do。

    1.1、读取列表中的值

      for命令最基本的用法就是遍历for命令自身所定义的一系列值。

     1 $ cat test1
     2 #!/bin/bash
     3 # basic for command
     4 for test in Alabama Alaska Arizona Arkansas California Colorado
     5 do
     6   echo The next state is $test
     7 done
     8 $ ./test1
     9 The next state is Alabama
    10 The next state is Alaska
    11 The next state is Arizona
    12 The next state is Arkansas
    13 The next state is California
    14 The next state is Colorado
    15 $

      每次for命令遍历值列表,它都会将列表中的下个值赋给$test变量。$test变量可以像for命令语句中的其他脚本变量一样使用。在最后一次迭代后,$test变量的值会在shell脚本的剩余部分一直保持有效。它会一直保持最后一次迭代的值(除非你修改了它)。

     1 $ cat test1b
     2 #!/bin/bash
     3 # testing the for variable after the looping
     4 for test in Alabama Alaska Arizona Arkansas California Colorado
     5 do
     6   echo "The next state is $test"
     7 done
     8   echo "The last state we visited was $test"
     9   test=Connecticut
    10   echo "Wait, now we're visiting $test"
    11 $ ./test1b
    12 The next state is Alabama
    13 The next state is Alaska
    14 The next state is Arizona
    15 The next state is Arkansas
    16 The next state is California
    17 The next state is Colorado
    18 The last state we visited was Colorado
    19 Wait, now we're visiting Connecticut
    20 $

      $test变量保持了其值,也允许我们修改它的值,并在for命令循环之外跟其他变量一样使用。

    1.2、读取列表中的复杂值

      事情并不会总像你在for循环中看到的那么简单。有时会遇到难处理的数据。下面是给shell脚本程序员带来麻烦的典型例子。

     1 $ cat badtest1
     2 #!/bin/bash
     3 # another example of how not to use the for command
     4 for test in I don't know if this'll work
     5 do
     6   echo "word:$test"
     7 done
     8 $ ./badtest1
     9 word:I
    10 word:dont know if thisll
    11 word:work
    12 $

      真麻烦。shell看到了列表值中的单引号并尝试使用它们来定义一个单独的数据值,这真是把事情搞得一团糟。

      有两种办法可解决这个问题:

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

      这两种解决方法并没有什么出奇之处,但都能解决这个问题。

     1 $ cat test2
     2 #!/bin/bash
     3 # another example of how not to use the for command
     4 for test in I don't know if "this'll" work
     5 do
     6   echo "word:$test"
     7 done
     8 $ ./test2
     9 word:I
    10 word:don't
    11 word:know
    12 word:if
    13 word:this'll
    14 word:work
    15 $

      在第一个有问题的地方添加了反斜线字符来转义don't中的单引号。在第二个有问题的地方将this'll用双引号圈起来。两种方法都能正常辨别出这个值。
      你可能遇到的另一个问题是有多个词的值。记住,for循环假定每个值都是用空格分割的。如果有包含空格的数据值,你就陷入麻烦了。

     1 $ cat badtest2
     2 #!/bin/bash
     3 # another example of how not to use the for command
     4 for test in Nevada New Hampshire New Mexico New York North Carolina
     5 do
     6   echo "Now going to $test"
     7 done
     8 $ ./badtest1
     9 Now going to Nevada
    10 Now going to New
    11 Now going to Hampshire
    12 Now going to New
    13 Now going to Mexico
    14 Now going to New
    15 Now going to York
    16 Now going to North
    17 Now going to Carolina
    18 $

      这不是我们想要的结果。for命令用空格来划分列表中的每个值。如果在单独的数据值中有空格,就必须用双引号将这些值圈起来。

     1 $ cat test3
     2 #!/bin/bash
     3 # an example of how to properly define values
     4 for test in Nevada "New Hampshire" "New Mexico" "New York"
     5 do
     6   echo "Now going to $test"
     7 done
     8 $ ./test3
     9 Now going to Nevada
    10 Now going to New Hampshire
    11 Now going to New Mexico
    12 Now going to New York
    13 $

      现在for命令可以正确区分不同值了。另外要注意的是,在某个值两边使用双引号时,shell并不会将双引号当成值的一部分。

    1.3、从变量读取列表

      通常shell脚本遇到的情况是,你将一系列值都集中存储在了一个变量中,然后需要遍历变量中的整个列表。也可以通过for命令完成这个任务。

     1 $ cat test4
     2 #!/bin/bash
     3 # using a variable to hold the list
     4 list="Alabama Alaska Arizona Arkansas Colorado"
     5 list=$list" Connecticut"
     6 for state in $list
     7 do
     8   echo "Have you ever visited $state?"
     9 done
    10 $ ./test4
    11 Have you ever visited Alabama?
    12 Have you ever visited Alaska?
    13 Have you ever visited Arizona?
    14 Have you ever visited Arkansas?
    15 Have you ever visited Colorado?
    16 Have you ever visited Connecticut?
    17 $

      $list 变量包含了用于迭代的标准文本值列表。注意,代码还是用了另一个赋值语句向 $list 变量包含的已有列表中添加(或者说是拼接)了一个值。这是向变量中存储的已有文本字符串尾部添加文本的一个常用方法。

    1.4、从命令读取值

      生成列表中所需值的另外一个途径就是使用命令的输出。可以用命令替换来执行任何能产生输出的命令,然后在for命令中使用该命令的输出。

     1 $ cat test5
     2 #!/bin/bash
     3 # reading values from a file
     4 file="states"
     5 for state in $(cat $file)
     6 do
     7   echo "Visit beautiful $state"
     8 done
     9 $ cat states
    10 Alabama
    11 Alaska
    12 Arizona
    13 Arkansas
    14 Colorado
    15 Connecticut
    16 Delaware
    17 Florida
    18 Georgia
    19 $ ./test5
    20 Visit beautiful Alabama
    21 Visit beautiful Alaska
    22 Visit beautiful Arizona
    23 Visit beautiful Arkansas
    24 Visit beautiful Colorado
    25 Visit beautiful Connecticut
    26 Visit beautiful Delaware
    27 Visit beautiful Florida
    28 Visit beautiful Georgia
    29 $

      这个例子在命令替换中使用了cat命令来输出文件states的内容。你会注意到states文件中每一行有一个州,而不是通过空格分隔的。for命令仍然以每次一行的方式遍历了cat命令的输出,假定每个州都是在单独的一行上。但这并没有解决数据中有空格的问题。如果你列出了一个名字中有空格的州,for命令仍然会将每个单词当作单独的值。

    说明 test5的代码范例将文件名赋给变量,文件名中没有加入路径。这要求文件和脚本位于同一个目录中。如果不是的话,你需要使用全路径名(不管是绝对路径还是相对路径)来引用文件位置。

    1.5、更改字段分隔符

      造成这个问题的原因是特殊的环境变量IFS,叫作内部字段分隔符(internal field separator)。IFS环境变量定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将下列字符当作字段分隔符:

    •  空格
    •  制表符
    •  换行符

      如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。在处理可能含有空格的数据(比如文件名)时,这会非常麻烦,就像你在上一个脚本示例中看到的。
      要解决这个问题,可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段分隔符的字符。例如,如果你想修改IFS的值,使其只能识别换行符,那就必须这么做:

    1 IFS=$'
    '

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

     1 $ cat test5b
     2 #!/bin/bash
     3 # reading values from a file
     4 file="states"
     5 IFS=$'
    '
     6 for state in $(cat $file)
     7 do
     8   echo "Visit beautiful $state"
     9 done
    10 $ ./test5b
    11 Visit beautiful Alabama
    12 Visit beautiful Alaska
    13 Visit beautiful Arizona
    14 Visit beautiful Arkansas
    15 Visit beautiful Colorado
    16 Visit beautiful Connecticut
    17 Visit beautiful Delaware
    18 Visit beautiful Florida
    19 Visit beautiful Georgia
    20 Visit beautiful New York
    21 Visit beautiful New Hampshire
    22 Visit beautiful North Carolina
    23 $

      现在,shell脚本旧能够使用列表中含有空格的值了。

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

    1 IFS.OLD=$IFS
    2 IFS=$'
    '

      <在代码中使用新的IFS值>

    IFS=$IFS.OLD

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

      还有其他一些IFS环境变量的绝妙用法。假定你要遍历一个文件中用冒号分隔的值(比如在/etc/passwd文件中)。你要做的就是将IFS的值设为冒号。

    1 IFS=:

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

    1 IFS=$'
    ':;"

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

    1.6、用通配符读取目录

      最后,可以用for命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中使用通配符。它会强制shell使用文件扩展匹配。文件扩展匹配是生成匹配指定通配符的文件名或路径名的过程。
      如果不知道所有的文件名,这个特性在处理目录中的文件时就非常好用。

     1 $ cat test6
     2 #!/bin/bash
     3 # iterate through all the files in a directory
     4 for file in /home/rich/test/*
     5 do
     6   if [ -d "$file" ]
     7   then
     8     echo "$file is a directory"
     9   elif [ -f "$file" ]
    10   then
    11     echo "$file is a file"
    12   fi
    13 done
    14 $ ./test6
    15 /home/rich/test/dir1 is a directory
    16 /home/rich/test/myprog.c is a file
    17 /home/rich/test/myprog is a file
    18 /home/rich/test/myscript is a file
    19 /home/rich/test/newdir is a directory
    20 /home/rich/test/newfile is a file
    21 /home/rich/test/newfile2 is a file
    22 /home/rich/test/testdir is a directory
    23 /home/rich/test/testing is a file
    24 /home/rich/test/testprog is a file
    25 /home/rich/test/testprog.c is a file
    26 $

      for命令会遍历/home/rich/test/*输出的结果。该代码用test命令测试了每个条目(使用方括号方法),以查看它是目录(通过-d参数)还是文件(通过-f参数)(参见上一篇博文)。
      注意,我们在这个例子的if语句中做了一些不同的处理:

    1 if [ -d "$file" ]

      在Linux中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将 $file 变量用双引号圈起来。如果不这么做,遇到含有空格的目录名或文件名时就会有错误产生。

    1 ./test6: line 6: [: too many arguments
    2 ./test6: line 9: [: too many arguments

      在test命令中,bash shell会将额外的单词当作参数,进而造成错误。
      也可以在for命令中列出多个目录通配符,将目录查找和列表合并进同一个for语句。

     1 $ cat test7
     2 #!/bin/bash
     3 # iterating through multiple directories
     4 for file in /home/rich/.b* /home/rich/badtest
     5 do
     6   if [ -d "$file" ]
     7   then
     8     echo "$file is a directory"
     9   elif [ -f "$file" ]
    10   then
    11     echo "$file is a file"
    12   else
    13     echo "$file doesn't exist"
    14   fi
    15 done
    16 $ ./test7
    17 /home/rich/.backup.timestamp is a file
    18 /home/rich/.bash_history is a file
    19 /home/rich/.bash_logout is a file
    20 /home/rich/.bash_profile is a file
    21 /home/rich/.bashrc is a file
    22 /home/rich/badtest doesn't exist
    23 $

      for语句首先使用了文件扩展匹配来遍历通配符生成的文件列表,然后它会遍历列表中的下一个文件。可以将任意多的通配符放进列表中。

    警告 注意,你可以在数据列表中放入任何东西。即使文件或目录不存在,for语句也会尝试处理列表中的内容。在处理文件或目录时,这可能会是个问题。你无法知道你正在尝试遍历的目录是否存在:在处理之前测试一下文件或目录总是好的。

    2、C 语言风格的for 命令

      如果你从事过C语言编程,可能会对bash shell中for命令的工作方式有点惊奇。在C语言中,for循环通常定义一个变量,然后这个变量会在每次迭代时自动改变。通常程序员会将这个变量用作计数器,并在每次迭代中让计数器增一或减一。bash的for命令也提供了这个功能。

    2.1、C 语言的for 命令

      C语言的for命令有一个用来指明变量的特定方法,一个必须保持成立才能继续迭代的条件,以及另一个在每个迭代中改变变量的方法。当指定的条件不成立时,for循环就会停止。条件等式通过标准的数学符号定义。比如,考虑下面的C语言代码:

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

      这段代码产生了一个简单的迭代循环,其中变量i作为计数器。第一部分将一个默认值赋给该变量。中间的部分定义了循环重复的条件。当定义的条件不成立时,for循环就停止迭代。最后一部分定义了迭代的过程。在每次迭代之后,最后一部分中定义的表达式会被执行。在本例中,i变量会在每次迭代后增一。

      bash shell也支持一种for循环,它看起来跟C语言风格的for循环类似,但有一些细微的不同,其中包括一些让shell脚本程序员困惑的东西。以下是bash中C语言风格的for循环的基本格式。

    1 for (( variable assignment ; condition ; iteration process ))

      C语言风格的for循环的格式会让bash shell脚本程序员摸不着头脑,因为它使用了C语言风格的变量引用方式而不是shell风格的变量引用方式。C语言风格的for命令看起来如下。

    1 for (( a = 1; a < 10; a++ ))

      注意,有些部分并没有遵循bash shell标准的for命令:

    • 变量赋值可以有空格;
    • 条件中的变量不以美元符开头;
    • 迭代过程的算式未用expr命令格式。

      shell开发人员创建了这种格式以更贴切地模仿C语言风格的for命令。这虽然对C语言程序员来说很好,但也会把专家级的shell程序员弄得一头雾水。在脚本中使用C语言风格的for循环时要小心。
      以下例子是在bash shell程序中使用C语言风格的for命令。

     1 $ cat test8
     2 #!/bin/bash
     3 # testing the C-style for loop
     4 for (( i=1; i <= 10; i++ ))
     5 do
     6   echo "The next number is $i"
     7 done
     8 $ ./test8
     9 The next number is 1
    10 The next number is 2
    11 The next number is 3
    12 The next number is 4
    13 The next number is 5
    14 The next number is 6
    15 The next number is 7
    16 The next number is 8
    17 The next number is 9
    18 The next number is 10
    19 $

      for 循环通过定义好的变量(本例中是变量 i )来迭代执行这些命令。在每次迭代中,$i 变量包含了 for 循环中赋予的值。在每次迭代后,循环的迭代过程会作用在变量上,在本例中,变量增一。

    2.2、使用多个变量

      C语言风格的for命令也允许为迭代使用多个变量。循环会单独处理每个变量,你可以为每个变量定义不同的迭代过程。尽管可以使用多个变量,但你只能在for循环中定义一种条件。

     1 $ cat test9
     2 #!/bin/bash
     3 # multiple variables
     4 for (( a=1, b=10; a <= 10; a++, b-- ))
     5 do
     6   echo "$a - $b"
     7 done
     8 $ ./test9
     9 1 - 10
    10 2 - 9
    11 3 - 8
    12 4 - 7
    13 5 - 6
    14 6 - 5
    15 7 - 4
    16 8 - 3
    17 9 - 2
    18 10 - 1
    19 $

      变量a和b分别用不同的值来初始化并且定义了不同的迭代过程。循环的每次迭代在增加变量a的同时减小了变量b。

  • 相关阅读:
    java之元数据(metadata)
    悲观锁(Pessimistic Locking)和乐观锁
    新建了springboot项目在包下右键创建class时无class选项
    idea创建一个springboot项目
    处理百万级以上的数据提高查询速度的方法
    写入文件
    WCf客户端测试
    WCF客户端代理
    WCF之Windows宿主(可安装成服务自动并启动)
    戴上耳机,全世界都是你的
  • 原文地址:https://www.cnblogs.com/Reverse-xiaoyu/p/13268104.html
Copyright © 2011-2022 走看看