【问题标题】:Iterate over lines instead of words in a for loop of shell script在 shell 脚本的 for 循环中迭代行而不是单词
【发布时间】:2012-05-25 05:02:56
【问题描述】:

以下是读取框中所有 DSF 的 shell 脚本。但是由于该行有空格,因此将它们显示在不同的行中。 看不懂ioscan -m dsf的朋友,换成ls -ltr,输出就是权限和名字显示在不同的行,但我希望它们在同一行。

#!/usr/bin/ksh

for a in `ioscan -m dsf`
do
 echo  $a
done

【问题讨论】:

    标签: linux shell


    【解决方案1】:

    for 循环不是为循环“行”而设计的。相反,它会遍历“单词”。

    简短术语:“行”是由换行符分隔的内容。 “单词”是由空格(和换行符等)分隔的东西。在 bash 术语中,“单词”被称为“字段”。

    循环遍历行的惯用方法是将while 循环与read 结合使用。

    ioscan -m dsf | while read -r line
    do
      printf '%s\n' "$line"
    done
    

    请注意,由于管道,while 循环位于子shell 中。这可能会导致与变量范围的一些混淆。在 bash 中,您可以使用进程替换来解决此问题。

    while read -r line
    do
      printf '%s\n' "$line"
    done < <(ioscan -m dsf)
    

    另见http://mywiki.wooledge.org/BashFAQ/024


    for 循环使用$IFS 变量中的字符作为分隔符来拆分要循环的内容。 IFS 是内部场分离器的缩写。通常$IFS 包含一个空格、一个制表符和一个换行符。这意味着for 循环将遍历“单词”,而不是行。

    如果您坚持使用 for 循环遍历行,则必须将 $IFS 的值更改为仅换行符。但是如果你这样做,你必须保存 $IFS 的旧值并在循环后恢复它,因为许多其他的东西也依赖于 $IFS

    OLDIFS="$IFS"
    IFS=$'\n' # bash specific
    for line in $(ioscan -m dsf)
    do
      printf '%s\n' "$line"
    done
    IFS="$OLDIFS"
    

    在没有ANSI-C Quoting ($'\n') 的 POSIX shell 中,您可以这样做:

    IFS='
    '
    

    即:在引号之间添加一个实际的新行。

    或者,您可以使用子shell 来包含对$IFS 的更改:

    (
      # changes to variables in the subshell stay in the subshell
      IFS=$'\n'
      for line in $(ioscan -m dsf)
      do
        printf '%s\n' "$line"
      done
    )
    # $IFS is not changed outside of the subshell
    

    但要注意循环中的命令本身可能依赖于$IFS 的一些合理设置。然后你必须在执行命令之前恢复$IFS,并在下一个循环之前再次设置。我不建议搞乱$IFS。太多的命令依赖于$IFS 中的一些合理值,而更改它是一个无休止的噩梦,就是寻找晦涩难懂的错误。

    另见:

    【讨论】:

    • subshel​​l 解决方案非常棒!谢谢!
    【解决方案2】:

    用于

    for l in $()基于IFS进行分词:

    $ for l in $(printf %b 'a b\nc'); do echo "$l"; done
    a
    b
    c
    $ IFS=$'\n'; for l in $(printf %b 'a b\nc'); do echo "$l"; done
    a b
    c
    

    IFS以后不用的话,不用重新设置。

    for l in $() 也执行路径名扩展:

    $ printf %b 'a\n*\n' > file.txt
    $ IFS=$'\n'
    $ for l in $(<file.txt); do echo "$l"; done
    a
    file.txt
    $ set -f; for l in $(<file.txt); do echo "$l"; done; set +f
    a
    *
    

    如果IFS=$'\n',换行符被剥离和折叠:

    $ printf %b '\n\na\n\nb\n\n' > file.txt
    $ IFS=$'\n'; for l in $(<file.txt); do echo "$l"; done
    a
    b
    

    $(cat file.txt)(或$(&lt;file.txt))也将整个文件读入内存。

    使用读取

    没有 -r 的反斜杠用于续行并在其他字符之前删除:

    $ cat file.txt
    \1\\2\
    3
    $ cat file.txt | while read l; do echo "$l"; done
    1\23
    $ cat file.txt | while read -r l; do echo "$l"; done
    \1\\2\
    3
    

    IFS 中的字符从行的开头和结尾被剥离但不折叠:

    $ printf %b '1  2 \n\t3\n' | while read -r l; do echo "$l"; done
    1  2
    3
    $ printf %b ' 1  2 \n\t3\n' | while IFS= read -r l; do echo "$l"; done
     1  2 
        3
    

    如果最后一行不以换行符结尾,则 read 将 l 分配给它,但在循环体之前退出:

    $ printf 'x\ny' | while read l; do echo $l; done
    x
    $ printf 'x\ny' | while read l || [[ $l ]]; do echo $l; done
    x
    y
    

    如果while循环在管道中,它也在子shell中,所以变量在它之外是不可见的:

    $ x=0; seq 3 | while read l; do let x+=l; done; echo $x
    0
    $ x=0; while read l; do let x+=l; done < <(seq 3); echo $x
    6
    $ x=0; x=8 | x=9; echo $x
    0
    

    【讨论】:

      【解决方案3】:

      您基本上需要使用IFS=$'\n'grep -x 而不是grep,因为它将像等于运算符而不是像运算符一样工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-20
        • 1970-01-01
        • 1970-01-01
        • 2022-05-17
        • 1970-01-01
        • 1970-01-01
        • 2019-10-28
        • 2021-02-24
        相关资源
        最近更新 更多