bash:从管道脚本中的 tty 读取

bash:从管道脚本中的 tty 读取

我正在处理的两个 bash 脚本遇到了一些麻烦:

脚本文件

#!/bin/bash
while true; do
    read -p "script: read: var: " var
    echo "script: write: var: $var"
done

管道.sh

#!/bin/bash
while read line; do
        echo "pipe: read: line: (( $line ))"
        read -p "pipe: read var: " var < /dev/tty
        echo "pipe: write: var: $var"
done< <(cat) 

当执行script.sh并将输出传输到我时,pipe.sh我得到以下输出:

$ ./script.sh | ./pipe.sh
1: script: read: var: 123   # user entering '123'
2: script: read: var: pipe: read: line: (( script: write: var: 123 ))
3: pipe: read var: 321      # user entering '321'
4: script: read: var: 456   # user entering '456'
5: pipe: write: var: 456
6: pipe: read: line: (( script: write: var: 321 ))
7: pipe: read var: 

正如您所看到的,在到达第 4 行之前,一切似乎都正常。我原以为第 4 行pipe: write: var: 321来自pipe.sh。相反,我从 得到提示script.sh

输入字符串“456”后,将执行先前预期的行,但字符串错误(预期:“321”,得到“456”)。此外,第 6 行不打印“456”,而是打印“321”。

这里有些事情完全不对劲。关于如何解决这个问题以及为什么会发生这种情况有什么建议吗?

更新:

本质上我希望管道的工作方式与下面的代码相同。

脚本1.sh

#!/bin/bash
while true; do
  read -p "val1: " val1
  script2.sh "${val1}"
done

脚本2.sh

#!/bin/bash
val1="${1}"
read -p "val2: " val2
echo "${val1} ${val2}"

但是,我不想硬编码script2.shscript1.sh.我可以script2.sh作为参数传递给,script1.sh但我最初认为管道会是更好的解决方案?

答案1

read -p两者中的调用和script.shpipe.sh当前终端读取,并且由于管道中的命令并行运行,因此您不能假设它们中的哪一个首先抢夺用户输入的数据。

from可以发出提示,但用户输入的字符串可以由from读取read -p,反之亦然。script.shread -ppipe.sh

在像这样的管道中a | bb可以很容易地等待输入,a然后再继续进行,但反之则不然:由于管道正在缓冲,因此必须在注意到没有读取任何数据a之前写入大量数据。b

解决这个问题的一种方法是将 stdoutb与 stdin连接a在一种“循环管道”中,并修改a( script.sh) 以等待来自 stdin 的输入,就像b( pipe.sh) 所做的那样。

由于 shell 语言的限制,您应该使用命名管道为了那个原因。简单的例子:

cat > circpipe <<'EOT'; chmod 755 circpipe
fifo=$(mktemp -u)
mkfifo "$fifo" || exit 1
exec 3<>"$fifo" >"$fifo" <"$fifo"
rm "$fifo"
echo trigger
"$1" | "$2"
EOT

cat > pipe-head <<'EOT'; chmod 755 pipe-head
while read next; do
        read -p "HEAD's prompt>> " var </dev/tty || exit
        echo "$var"
done
EOT

cat > pipe-tail <<'EOT'; chmod 755 pipe-tail
while read input; do
        echo >&2 "  TAIL'input: $input"
        read -p "  TAIL's prompt>> " var </dev/tty
        echo >&2 "  TAIL processing <$var>"
        echo next       # trigger the head of the pipeline
done
EOT
./circpipe ./pipe-head ./pipe-tail
HEAD's prompt>> foo
  TAIL'input: foo
  TAIL's prompt>> bar
  TAIL processing <bar>
HEAD's prompt>> baz
  TAIL'input: baz
  TAIL's prompt>> quux
  TAIL processing <quux>
HEAD's prompt>> ^D$

circpipe脚本可以制作成一个更通用的工具,它可以接受常规的 shell 命令,并且它的“尾部”也可以跳出循环。

与上面的示例不同,默认情况下这不会“启动”循环;为此,-command应该使用一个参数。使用示例:

./circpipe -echo './pipe-head | stdbuf -oL sed s/e/o/g | ./pipe-tail'
HEAD's prompt>> pee
  TAIL'input: poo
  TAIL's prompt>> lol
  TAIL processing <lol>
HEAD's prompt>>^D

环形管

#! /bin/sh
# usage: circpipe [-start_command] shell_command
# run 'shell_command' with its stdin connected to its stdout
# via a FIFO
# if 'start_command' is given, run it before `shell_command`
# with its stdout redirected to the same FIFO
[ "$#" -gt 0 ] || exit 0
fifo=$(mktemp -u)
mkfifo "$fifo" || exit 1
exec 3<>"$fifo" >"$fifo" 3<"$fifo"
rm "$fifo"
case $1 in -*)  eval "${1#-}"; shift; esac
IFS='|'; eval "<&3 $* &"
exec >&-
wait

相关内容