bash 为什么管道输入到“读取”仅在输入“同时读取...”构造时才有效?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13763942/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 23:04:16  来源:igfitidea点击:

Why piping input to "read" only works when fed into "while read ..." construct?

bashwhile-looppipepiping

提问by huoneusto

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

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

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

And the result is:

结果是:

-

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:

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

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

Seems to work, the result is:

似乎有效,结果是:

first-second

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

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

采纳答案by F. Hauri

How to do a loop against stdinand get result stored in a variable

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

Under bash(and other shellalso), when you pipe something by using |to another command, you will implicitely create a fork, a subshell who's a child of current session and who can't affect current session's environ.

bash(以及其他shell)下,当您通过使用管道将某些东西|传递给另一个命令时,您将隐式地创建一个fork,一个作为当前会话的子级并且不能影响当前会话环境的子 shell。

So this:

所以这:

TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
  while read A B;do
      ((TOTAL+=A-B))
      printf "%3d - %3d = %4d -> TOTAL= %4d\n" $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

Where computed TOTALcould'nt be reused in main script.

无法在主脚本中重用计算出的TOTAL 的地方。

Inverting the fork

倒叉

By using bashProcess Substitution, Here Documentsor Here Strings, you could inverse the fork:

通过使用bash Process SubstitutionHere DocumentsHere Strings,您可以反转分叉:

Here strings

这里的字符串

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

echo $B
second

Here Documents

这里的文件

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

outside of the loop:

循环之外:

echo : $C
: third-fourth

Here Commands

这里命令

TOTAL=0
while read A B;do
    ((TOTAL+=A-B))
    printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
  done < <(
    printf "%s %s\n" 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

Now you could use $TOTALin your main script.

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

Piping to a command list

管道到命令列表

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

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

printf "%s %s\n" 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\n" $A $B $[A-B] $TOTAL
    done
    echo "Out of the loop total:" $TOTAL
  }

Will give:

会给:

  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

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

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

Using lastpipebash option

使用lastpipebash 选项

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

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

shopt -s lastpipe           # Set *lastpipe* option
set +m                      # Disabling job control
TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
  while read A B;do
      ((TOTAL+=A-B))
      printf "%3d - %3d = %4d -> TOTAL= %4d\n" $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 standardand won't help to make script readable. Also disabling job control seem expensive for accessing this behaviour.

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

Note:Job controlis enabled by default only in interactivesessions. So set +mis not required in normal scripts.

注意:默认情况下仅在交互式会话中启用作业控制。所以在普通脚本中不需要。set +m

So forgotten set +min 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...

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

回答by pbhd

First, this pipe-chain is executed:

首先,执行这个管道链:

echo first second | read A B

then

然后

echo $A-$B

Because the read A Bis executed in a subshell, A and B are lost. If you do this:

因为read A B在子shell中执行,A和B都丢失了。如果你这样做:

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

then both read A Band echo $A-$Bare executed in the same subshell (see manpage of bash, search for (list)

然后既read A Becho $A-$B在相同的子shell被执行(见的bash手册页,搜索(list)

回答by immotus

a much cleaner work-around...

一个更清洁的解决方法......

read -r a b < <(echo "$first $second")
echo "$a $b"

This way, read isn't executed in a sub-shell (which would clear the variables as soon as that sub-shell has ended). Instead, the variables you want to use are echoed in a sub-shell that automatically inherits the variables from the parent shell.

这样, read 不会在子 shell 中执行(它会在子 shell 结束后立即清除变量)。相反,您要使用的变量会在子 shell 中回显,该子 shell 会自动从父 shell 继承变量。

回答by Martin Kealey

What you are seeing is the separation between processes: the readoccurs in a subshell - a separate process which cannot alter the variables in the main process (where echocommands later occur).

您所看到的是进程之间的分离:read发生在子外壳中 - 一个单独的进程,它不能改变主进程中的变量(echo命令稍后发生)。

A pipeline (like A | B) implicitly places each component in a sub-shell (a separate process), even for built-ins (like read) that usually run in the context of the shell (in the same process).

管道(如A | B)隐式地将每个组件放置在一个子外壳(一个单独的进程)中,即使对于read通常在外壳的上下文中(在同一进程中)运行的内置函数(如)也是如此。

The difference in the case of "piping into while" is an illusion. The same rule applies there: the loop is the second half of a pipeline, so it's is in a subshell, but the wholeloop is in the samesubshell, so the separation of processes does not apply.

“piping into while”的差异是一种错觉。同样的规则适用于那里:循环是管道的后半部分,所以它在一个子壳中,但整个循环在同一个子壳中,所以进程的分离不适用。