为啥管道输入到“read”只有在输入“while read ...”结构时才有效? [复制]

Posted

技术标签:

【中文标题】为啥管道输入到“read”只有在输入“while read ...”结构时才有效? [复制]【英文标题】:Why piping input to "read" only works when fed into "while read ..." construct? [duplicate]为什么管道输入到“read”只有在输入“while read ...”结构时才有效? [复制] 【发布时间】:2012-11-25 16:21:52 【问题描述】:

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

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

结果是:

-

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

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

似乎可行,结果是:

first-second

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

【问题讨论】:

这是 BashFAQ #24:mywiki.wooledge.org/BashFAQ/024 【参考方案1】:

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

在bash(以及其他shell)下,当您通过| 将某些内容传递给另一个命令时,您将隐式创建一个fork,这是一个子shell,它是current 的子shell会议。 subshel​​l 不能影响当前会话的环境。

所以这个:

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

不会给出预期的结果! :

  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 不能在主脚本中重复使用。

倒叉

通过使用bashProcess SubstitutionHere DocumentsHere Strings,您可以反转 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\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

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

管道到一个命令列表

但如果只针对 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
  

将给予:

  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 不能用于 主脚本(在最后一个右大括号 之后)。

使用 lastpipe bash 选项

正如@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

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

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

如果在控制台中运行或在脚本中运行,那么在脚本中忘记set +m 会产生不同的行为。这不会使其易于理解或调试...

【讨论】:

谢谢。在我发布我的问题后不久,我意识到while 循环仍然在子代而不是父代中执行,并且我不能在循环外使用AB 这里有一点需要注意——有些 shell 支持为管道的其他组件创建子 shell,并在右侧使用现有的 shell。这是 bash 中的 shopt -s lastpipe; ksh88 和 ksh93 开箱即用的行为相似。 可能值得注意的是,作业控制在非交互式 shell 中默认关闭(因此,在所有未明确重新启用它的脚本中)。因此,您注意到的限制并不适用于所有情况。 (但可以肯定的是,在交互式和非交互式 shell 之间工作不同的代码本身就是一个缺点)。 这个答案非常彻底,但给人的印象是,当您想将 read 放在类似管道的语法的尾部时,仍然无法使用 read 捕获标准输出单线。这就是OP试图做的。正如其他答案所示,您可以使用子外壳或分组来执行此操作。但是有没有办法既容易又正确地做到这一点? ——也就是说,没有循环、子shell或组,并且有进程替换? @F.Hauri 当我阅读问题本身的正文时,而不仅仅是标题,在我看来,OP 正试图将 read 放在最后,并且想知道为什么他或她必须使用尽管。他们没有尝试使用while。他们试图把 read 放在最后。也许我假设太多了。【参考方案2】:

一个更干净的解决方法...

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

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

【讨论】:

对于可能对 tldp.org/LDP/abs/html/process-sub.html 如果我想 (echo "$first"; echo "$second") 进入 A 和 B 怎么办? (或者更确切地说,一个输出两行的命令,我希望第一行进入 A,第二行进入 B) 您还可以通过执行以下操作来防止生成子shell 只是为了运行 echo:read -r a b &lt;&lt;&lt; "$first $second"【参考方案3】:

首先,这个管道链被执行:

echo first second | read A B

然后

echo $A-$B

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

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

那么read A Becho $A-$B 都在同一个子shell 中执行(参见bash 的联机帮助页,搜索(list)

【讨论】:

顺便说一句,你不需要 subshel​​l。您可以简单地使用分组。 @anishsane:这很奇怪:如果你打开两个控制台,首先点击 tty 以了解使用了哪个 pty,而不是第二个 watch ps --tty pts/NN(其中 NN 在第一个控制台的答案中) .比第一种,尝试:echo | (sleep 10)echo | sleep 10 ; ,你会看到第二种语法会生成两个分叉,而第一次只反弹一个子shell。 如果您想使用分组运算符而不是创建子shell,则需要记住空格和分号:echo something | read myvar; echo $myvar; 谢谢,最好的答案。【参考方案4】:

您看到的是进程之间的分离:read 出现在子 shell 中 - 一个无法更改主进程中的变量的单独进程(稍后会出现 echo 命令)。

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

“进入while”情况的区别是一种错觉。相同的规则适用于那里:循环是管道的后半部分,因此它位于子外壳中,但 整个 循环位于 same 子外壳中,因此分离的进程不适用。

【讨论】:

语法:A | B 将为B 创建一个分离的进程!不是子外壳中的每个组件A 将保留在主(父)外壳中! (除非shopt -s lastpipe A 将在子shell 中运行,但没有B!) @F.Hauri 不,你倒退了。 A 将始终位于子shell中; B 将在子外壳中除非您使用shopt -s lastpipe ( set x y z ; set a b c | set d e f ; echo "$@" ) x y z ( shopt -s lastpipe ; set x y z ; set a b c | set d e f ; echo "$@" ) d e f

以上是关于为啥管道输入到“read”只有在输入“while read ...”结构时才有效? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的输入通过管道发送到进程时会延迟?

在无限while循环中从管道输入中读取行

为啥这个while循环在输入的每个字符之后重复?

如何使用linux read命令

使用FileReader进行文本文件操作时,read方法的返回值为啥时,表示读取到文件

linux 中 read 命令是啥意思