为什么 Bash 的 read 内置函数不会通过管道从 yes 命令获取输入,但会与进程替换一起使用?

问题描述 投票:0回答:2

TL;博士

我想了解为什么

yes
命令可以与大多数从标准输入读取的工具和脚本一起正常工作,但无法与 Bash 自己的 read 内置命令一起使用,除非使用 进程替换 或一组复杂的 shell选项。我发现这种行为令人惊讶并且记录很少,尽管我认为这与 Bash 管道通常创建子 shell 的方式有关。

Bash 的内置读取

我在 macOS 11.6.3 上使用 Bash 5.1.16(1)-release。因此,

yes
命令来自 BSD,但我在各种 Linux 系统上看到了相同的行为。具体来说,
yes
的输出可以成功地通过管道传输到从标准输入读取的 shell 脚本和工具中,但由于某种原因,我无法使用 Bash
read
内置函数来填充变量。由于
yes
使用标准输出,并且
read
默认为标准输入,因此我希望以下内容填充内置的默认 REPLY 变量:

yes | read
echo "$REPLY"

但是,REPLY甚至没有设置:

$ declare -p REPLY
bash: declare: REPLY: not found

假设问题是分隔符似乎没有帮助,并且下面代码中面向行的测试也没有证实这一点。如果缺少换行符,则以下任一面向字符的选项应该有效:

$ yes | read -n 1; declare -p REPLY
$ yes | read -N 1; declare -p REPLY

但是,在这两种情况下,Bash 都会报告

bash: declare: REPLY: not found

请注意,即使我显式定义要填充的变量,问题也是一样的。这不是 read 的默认 REPLY 变量的问题;这似乎是内置期望获取输入的方式的问题。

进程替换、一些复杂的命令和非内置命令都可以正常工作

另一方面,Bash 的进程替换效果很好:

$ read < <(yes)
$ echo "$REPLY"
y

为什么它可以与进程替换一起使用,但不能与简单的管道一起使用?如果我尝试从复杂命令中访问REPLY,它也可以工作。例如,确保使用 unset REPLY 取消设置

REPLY
变量后:

$ unset REPLY
$ yes | { read; echo "$REPLY"; }
y

$ declare -p REPLY
bash: declare: REPLY: not found

显然,它也可以与其他采用标准输入的工具一起按预期工作。例如,使用 Perl 或 Ruby:

$ yes | perl -ne 'print; exit'
y

$ yes | ruby -nle 'pp $_; exit'
"y"

相关问题的部分答案

最后,根据隐藏在相关问题中的评论,看起来你可以使标准(左右)管道工作,如果你:

  1. 使用内置的set禁用
    作业控制
    ,并且
  2. 使用 shopt 启用 shell 的
    lastpipe
    选项。

例如:

$ shopt -s lastpipe; \
    set +m; \
    unset REPLY; \
    yes | read; \
    echo "$REPLY"
y

至少这将问题定义为与子 shell 相关的问题,而不是标准输入的问题,但它并没有真正解释为什么存在限制或作业控制与此到底有什么关系。如果这是 Bash 的预期行为和基本行为,那么它并不是很直观,我希望能对此语义有更好的解释(如果存在)。

bash pipe stdin built-in process-substitution
2个回答
1
投票
  • 管道创建子shell。子shell有自己的变量范围,当它们的命令(在本例中为
    read
    )完成执行时,变量范围结束。
  • 对于
    yes | read
    read
    正在将变量 (
    REPLY
    ) 写入子 shell 环境,一旦
    read
    完成执行,该环境就会超出范围。
  • 代替管道,从重定向 (
    read
    ) 文件或进程替换或从此处字符串 (
    <
    ) 向
    <<< 'string input'
    (或任何命令)提供输入是提供输入的方法,允许
    read 
    在当前执行环境(和变量范围)中运行。这意味着它创建的变量将持续存在。
  • yes | { read; echo "$REPLY"; }
    这是有效的,因为 read 和 echo 位于同一个命令块中,因此它们共享相同的子 shell(和变量范围)。
  • shopt -s lastpipe; yes | read; echo "$REPLY"
    之所以有效,是因为
    lastpipe
    的目的是使管道的最后一个命令在当前环境(而不是子shell)中执行。
  • 重申一下,这与
    read
    具体无关,完全与管道在子 shell 中运行这一事实有关。如果要修改当前环境,则需要在管道之外进行。 Bash 至少提供了进程替换来轻松重定向命令输出。

0
投票

TL;DR: 当使用

read -n1
然后使用
yes |
时,即使使用
set
shopt
,也不会削减它。我需要使用
echo "yyyyyy" |
,它就像一个魅力!

我尝试使用

yes
“编写”bash 脚本来为
read
提供默认答案,但惨败。

寻找我遇到的解决方案https://www.success-in-software.com/bash-automation-catch-with-built-in-read-and-yes/(甚至引用了这个SO主题) .

© www.soinside.com 2019 - 2024. All rights reserved.