当送入“而读...”结构为什么管道输入到“读”只适用?(Why piping input to “r

2019-06-21 18:08发布

我一直在试图读取输入这样从程序输出的环境变量:

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

其结果是:

-

A和B总是空的。 我读到的bash在子壳执行管道命令和基本上预防一种从管道输入到读出。 然而,以下内容:

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

似乎工作,其结果是:

first-second

可有人请解释什么是这里的逻辑是什么? 难道里面的命令while ... done结构在同一个shell作为实际执行的echo ,而不是在一个子shell?

Answer 1:

如何做一个循环对标准输入和获得的结果存储在变量

在庆典 (和其他外壳也),当你管什么用| 另一个命令,你会隐含创建一个 ,一个子shell谁是本届会议的一个孩子,谁也不能影响当前会话的ENVIRON。

所以这:

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

当计算 could'nt在主脚本被重用。

反相叉

通过使用bash的 进程替换这里的文档字符串在这里 ,你可以反叉:

这里串

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在你的主脚本

管道连接到命令列表

但对于工作仅针对标准输入 ,您可以创建一种脚本进的:

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在脚本运行,如果在一个控制台或如果在脚本中运行会产生不同的行为。 这会不会让这个容易理解和调试...



Answer 2:

首先,该管链被执行:

echo first second | read A B

然后

echo $A-$B

因为read AB在子Shell被执行时,A和B都将丢失。 如果你这样做:

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

那么这两个read ABecho $A-$B在同一子shell(见的bash的手册页,搜索执行(list)



Answer 3:

更清洁的变通......

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

通过这种方式,读取不在一个子shell(其将尽快为子shell结束清除变量)执行。 相反,你要使用的变量回荡在一个子shell自动继承父shell变量。



Answer 4:

你看到的是进程之间的分离:在read在子shell发生-单独的过程不能改变主处理(其中变量echo命令后发生)。

流水线(如A | B )隐式地在子壳的每个组件(一个独立的进程),即使是内置插件(比如read ),通常在shell的上下文中运行(在同一个进程)。

在“管道进入而”的情况下,所不同的是一种错觉。 该规则同样适用有:循环是管道下半年,所以它是在子shell,但整个循环是在同一子shell,这样的过程分离不适用。



文章来源: Why piping input to “read” only works when fed into “while read …” construct?