为什么管道输入要“读取"?仅在输入“while read ..."时才有效;构造? [英] Why piping input to "read" only works when fed into "while read ..." construct?

查看:25
本文介绍了为什么管道输入要“读取"?仅在输入“while read ..."时才有效;构造?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试从程序输出中读取输入到环境变量中,如下所示:

I've been trying to read input into environment variables from program output like this:

echo first second | read A B ; echo $A-$B 

结果是:

-

A 和 B 始终为空.我读到关于 bash 在子 shell 中执行管道命令的内容,这基本上阻止了一个人通过管道输入读取.但是,以下内容:

Both A and B are always empty. I read about bash executing piped commands in sub-shell and that basically preventing one from piping input to read. However, the following:

echo first second | while read A B ; do echo $A-$B ; done

好像可以,结果是:

first-second

有人可以解释一下这里的逻辑是什么吗?while ... done 构造中的命令实际上是在与 echo 相同的 shell 中执行的,而不是在子 shell 中执行的吗??

Can someone please explain what is the logic here? Is it that the commands inside the while ... done construct are actually executed in the same shell as echo and not in a sub-shell?

推荐答案

如何对stdin进行循环并将结果存储在变量中

(和其他 也是),当您通过 |,你将隐式地创建一个 fork,一个作为当前会话子进程的子 shell.子shell不能影响当前会话的环境.

How to do a loop against stdin and get result stored in a variable

Under bash (and other shell also), when you pipe something to another command via |, you will implicitly create a fork, a subshell that is a child of current session. The subshell can't affect current session's environment.

所以:

TOTAL=0
printf "%s %s
" 9 4 3 1 77 2 25 12 226 664 |
  while read A B;do
      ((TOTAL+=A-B))
      printf "%3d - %3d = %4d -> TOTAL= %4d
" $A $B $[A-B] $TOTAL
    done
echo final total: $TOTAL

不会给出预期的结果!:

won't give expected result! :

  9 -   4 =    5 -> TOTAL=    5
  3 -   1 =    2 -> TOTAL=    7
 77 -   2 =   75 -> TOTAL=   82
 25 -  12 =   13 -> TOTAL=   95
226 - 664 = -438 -> TOTAL= -343
echo final total: $TOTAL
final total: 0

计算出的 TOTAL 不能在主脚本中重用.

Where computed TOTAL could'nt be reused in main script.

通过使用 过程替换Here DocumentsHere Strings,你可以反转 fork:

By using bash Process Substitution, Here Documents or Here Strings, you could inverse the fork:

read A B <<<"first second"
echo $A
first

echo $B
second

这里的文件

while read A B;do
    echo $A-$B
    C=$A-$B
  done << eodoc
first second
third fourth
eodoc
first-second
third-fourth

循环之外:

echo : $C
: third-fourth

这里的命令

TOTAL=0
while read A B;do
    ((TOTAL+=A-B))
    printf "%3d - %3d = %4d -> TOTAL= %4d
" $A $B $[A-B] $TOTAL
  done < <(
    printf "%s %s
" 9 4 3 1 77 2 25 12 226 664
)
  9 -   4 =    5 -> TOTAL=    5
  3 -   1 =    2 -> TOTAL=    7
 77 -   2 =   75 -> TOTAL=   82
 25 -  12 =   13 -> TOTAL=   95
226 - 664 = -438 -> TOTAL= -343

# and finally out of loop:
echo $TOTAL
-343

现在您可以在主脚本中使用$TOTAL.

但是为了仅针对 stdin 工作,您可以在 fork 中创建一种脚本:

But for working only against stdin, you may create a kind of script into the fork:

printf "%s %s
" 9 4 3 1 77 2 25 12 226 664 | {
    TOTAL=0
    while read A B;do
        ((TOTAL+=A-B))
        printf "%3d - %3d = %4d -> TOTAL= %4d
" $A $B $[A-B] $TOTAL
    done
    echo "Out of the loop total:" $TOTAL
  }

会给:

  9 -   4 =    5 -> TOTAL=    5
  3 -   1 =    2 -> TOTAL=    7
 77 -   2 =   75 -> TOTAL=   82
 25 -  12 =   13 -> TOTAL=   95
226 - 664 = -438 -> TOTAL= -343
Out of the loop total: -343

注意:$TOTAL 不能用于主脚本(在最后一个右大括号 } 之后).

Note: $TOTAL could not be used in main script (after last right curly bracket } ).

正如@CharlesDuffy 正确指出的,有一个 bash 选项用于改变这种行为.但为此,我们必须首先禁用作业控制:

As @CharlesDuffy correctly pointed out, there is a bash option used to change this behaviour. But for this, we have to first disable job control:

shopt -s lastpipe           # Set *lastpipe* option
set +m                      # Disabling job control
TOTAL=0
printf "%s %s
" 9 4 3 1 77 2 25 12 226 664 |
  while read A B;do
      ((TOTAL+=A-B))
      printf "%3d - %3d = %4d -> TOTAL= %4d
" $A $B $[A-B] $TOTAL
    done

  9 -   4 =    5 -> TOTAL= -338
  3 -   1 =    2 -> TOTAL= -336
 77 -   2 =   75 -> TOTAL= -261
 25 -  12 =   13 -> TOTAL= -248
226 - 664 = -438 -> TOTAL= -686

echo final total: $TOTAL
-343

这行得通,但我(个人)不喜欢这样,因为这不是标准并且无助于使脚本可读.同样禁用作业控制对于访问此行为来说似乎很昂贵.

This will work, but I (personally) don't like this because this is not standard and won't help to make script readable. Also disabling job control seem expensive for accessing this behaviour.

注意:作业控制默认仅在交互式会话中启用.所以 set +m 在普通脚本中不是必需的.

Note: Job control is enabled by default only in interactive sessions. So set +m is not required in normal scripts.

因此,如果在控制台中运行或在脚本中运行,脚本中被遗忘的 set +m 会产生不同的行为.这不会使这更容易理解或调试...

So forgotten set +m in a script would create different behaviours if run in a console or if run in a script. This will not going to make this easy to understand or to debug...

这篇关于为什么管道输入要“读取"?仅在输入“while read ..."时才有效;构造?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆