【问题标题】:Why won't my grep-filtered string print from within a while-loop?为什么我的 grep 过滤字符串不能从 while 循环中打印出来?
【发布时间】:2017-05-02 20:39:16
【问题描述】:

试图让我的代码尽可能简单:

1:在while循环中使用echo的规则是什么? 我所有的 $a 和我的一些 $word 变量都被回显而不是我的 echo kk?

2:我的计数变量的范围是什么?为什么它在我的 while 循环中不起作用?我可以扩展变量以使其成为全局变量吗?

3:当我在最后一行中使用 grep 时,如果我按预期删除最后 $work 函数中的 grep 行并打印所有单词,则 $word cariable 仅打印传递行中的第一个单词。

count=1
while read a; do
    ((count=count+1))
    if [ $count -le 2 ]
    then
        echo $a
        echo kk
        for word in $a; do
            echo $word
        done
    fi
done < data.txt | grep Iteration

【问题讨论】:

  • 一些echo 命令被丢弃,因为您使用grep 过滤输出。例如:kk 不包含“迭代”,所以永远不会发出!
  • 非常感谢,它解释了很多奇怪的结果。我以为我在 data.txt 上使用 grep

标签: bash while-loop grep


【解决方案1】:

使用进程替换

a comment,你说:

我以为我在 data.txt 上使用了 grep (sic)

没有。您当前的管道通过 grep 而不是源文件传递循环的 results。为此,您需要重写重定向以使用process substitution。例如:

count=1
while read a; do
    ((count=count+1))
    if [ $count -le 2 ]
    then
        echo $a
        echo kk
        for word in $a; do
            echo $word
        done
    fi
done < <(fgrep Iteration data.txt)

【讨论】:

  • 由于fgrep is deprecated,我想在新代码中使用grep -F 是有意义的。
  • @TomFenech 您指向的文档是 1997 年的。如果它们现在还没有消失,我怀疑 fgrep 或 egrep 很快就会消失。我还没有遇到过不包含它们的现代 Linux 或 BSD 发行版(包括 macOS),但诚然是 YMMV。
  • 可能是因为大家一直在用! :^)
【解决方案2】:

@CodeGnome 回答了您的问题,但您的脚本还有其他问题会在某个时候再次困扰您。 (请参阅https://unix.stackexchange.com/questions/169716/why-is-using-a-shell-loop-to-process-text-considered-bad-practice 了解其中一些的讨论以及谷歌引用的 shell 变量)。只是不要这样做。 Shell 脚本仅用于对工具的调用排序,而用于处理文本的 UNIX 工具是 awk。在这种情况下,您需要稳健、便携和高效地完成这项工作:

awk '
/Iteration/ {
    if (++count <= 2) {
        print
        print "kk"
        for (i=1; i<=NF; i++) {
            print $i
        }
    }
}' data.txt

当然,如果在计数达到 2 时停止读取输入,效率会更高:

awk '
/Iteration/ {
    print
    print "kk"
    for (i=1; i<=NF; i++) {
        print $i
    }
    if (++count == 2) {
        exit
    }
}' data.txt

【讨论】:

    【解决方案3】:

    补充CodeGnome's helpful answer解释你的命令是如何实际工作的,以及为什么它没有按照你的意愿运行

    在 Bash 的语法中,输入重定向(例如 &lt; data.txt)是 单个 命令的一部分,而 |(管道符号)链接 多个 命令,来自从左到右,形成一个管道

    从技术上讲,while ... done ... &lt; data.txt | grep Iteration 是由 2 个命令组成的单个管道:

    • 带有输入重定向 (&lt; data.txt) 的单个 复合命令 (while ...; do ...; done),

    • 和一个简单命令 (grep Iteration),它通过管道的标准输入接收复合命令的标准输出输出。

    换句话说:

    • data.txt 的内容作为输入(通过标准输入)馈送到while 循环,

    • while 循环产生的任何 stdout 输出然后发送到下一个管道段,即grep 命令。


    相比之下,听起来您想将grep 应用到data.txt首先,并且只将匹配的行发送到while loop。 p>

    您有以下选项用于将命令的输出发送到另一个命令

    注意:为简洁起见,以下解决方案使用简化的 while 循环 - while 命令是单行还是跨多行无关紧要。
    此外,data.txt 不是使用输入重定向 (&lt; data.txt) 将文件内容传递给 grep,而是作为 文件名参数 传递。

    选项 1:将输出发送到while 循环的命令首先放在管道中

    grep 'Iteration' data.txt | while read -r a; do echo "$a"; done
    

    这种方法的缺点是您的 while 循环随后会在 subshel​​l 中运行(就像管道的所有段默认情况下一样),这意味着在您的while 命令中定义或修改的变量对于当前 shell 是不可见的。

    在 Bash v4.2+ 中,您可以通过运行 shopt -s lastpipe 解决此问题,这会告诉 Bash 运行 last 管道段 - while 命令这种情况 - 在 current shell 中。
    请注意,lastpipe 是 POSIX 标准的非标准 bash 扩展。 (要在 交互式 shell 中尝试此操作,您必须先使用 set +m 关闭作业控制。)


    选项 2:使用process substitution

    简单地说,进程替换 &lt;(...) 允许您将命令输出显示为临时文件的内容,该文件会自行清理。
    由于&lt;(...) 扩展为临时文件(FIFO's)的path,并且while 循环中的read 只接受stdin 输入,因此也必须应用输入重定向:&lt; &lt;(...):

    while read -r a; do echo "$a"; done < <(grep 'Iteration' data.txt)
    

    这种方法的优点是while 循环在current 子shell 中运行,因此在命令完成后任何变量定义或修改都保留在范围内。

    这种方法的潜在缺点是进程替换是对 POSIX 标准的非标准 bash 扩展(尽管 kshzsh 也支持它们)。


    选项 3:在此处文档中使用命令替换

    首先在管道中使用命令(选项 1)是符合 POSIX 的方法,但不允许您修改 current shell 中的变量(并且 Bash 的 lastpipe 选项不是POSIX 兼容)。

    将命令输出发送到在 当前 shell 中运行的命令的唯一 POSIX 兼容方法是在双引号here-document:

    while read -r a; do echo "$a"; done <<EOF
    $(grep 'Iteration' data.txt)
    EOF
    

    简化您的代码并使其更加健壮

    您的代码的其余部分存在一些值得解决的不明显缺陷:

    • 双引号引用您的变量引用(例如,echo "$a" 而不是 echo $a),除非您特别希望将分词和通配符(文件名扩展)应用于值;分词和通配符是shell expansions的两种。

    • 同样,不要使用for 来迭代(必须不带引号的)变量引用(在你的情况下不要使用for word in $a),除非你想要globbing 应用于单个单词 - 看看运行 $a='one *'; for word in $a; do echo "$word"; done 时会发生什么
      您可以事先关闭通配符 (set -f),然后再打开 (set +f),但最好先使用 read -ra words ... 将单词读入 array,然后安全地迭代带有for word in "${words[@]}"; ... 的数组元素——注意数组变量引用周围的"..."

    • 始终使用-rread;没有它,很少使用 \-preprocessing 被应用,它会“吃掉”嵌入的 \ 字符。

    如果我们听从上面的建议,应用一些额外的调整,并使用进程替换将grep 的输出提供给while 循环,我们得到:

    count=1
    while read -r a; do # Note the -r
        if (( ++count <= 2 )); then
            echo "$a"
            # Split $a safely into words and store the words in
            # array variable ${words[@]}.
            read -ra words <<<"$a" # Note the -a to read into an *array*.
            # Loop over the words (elements of the array).
            # Note: To simply print the words, you could use 
            #       `printf '%s\n' "${words[@]}"`` instead of the loop.
            for word in "${words[@]}"; do
                echo "$word"
            done
        fi
    done < <(grep 'Iteration' data.txt)
    

    注意:正如所写,您根本不需要 循环,因为您总是在第一次迭代后退出。

    最后,作为较大输入集的一般替代方案,请考虑Ed Morton's helpful answer,由于使用awk 来处理您的输入文件,它要快得多,而在shell 代码中循环通常很慢

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-10
      • 1970-01-01
      • 2016-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多