有没有一种方法可以读取命令的控制台输出而无需重定向或通过管道传输其 STDOUT/STDERR?
重定向或管道的问题是某些命令在其 STDOUT 和/或 STDERR 被重定向时表现不同,例如颜色或某些格式被删除,或者更多相关的差异。旧tput
版本需要在常规控制台上使用 STDOUT 或 STDERR 才能读取其尺寸。此外,在管道的情况下,原始命令失去了控制原始 shell 的能力,例如exit
,无法从函数内部调用脚本,其输出通过管道传输,这是不可能的。
我想要实现的是执行一个命令,以便它直接将其输出打印到控制台,能够杀死/退出 shell,但解析/处理并以防万一记录其输出。tee
将是一个明显的解决方案,但它受到上述问题的影响。
我做了一些尝试read
,运行一个循环,尝试从命令的文件描述符中读取,或者/dev/stdout
,/dev/tty
但在所有情况下,显示的命令输出中没有一行实际上是read
.
例如
#!/bin/bash
apt update 2>&1 & pid=$!
while [[ -f /proc/$pid/fd/1 ]] && read -r line
do
echo "$line" >> ./testfile
done < /proc/$pid/fd/1
还可以在后台运行 while 循环,在前台运行命令(最好是 IMO),例如:
#!/bin/bash
while read -r line
do
echo "$line" >> ./testfile
done < /dev/tty & pid=$!
apt update
kill $pid
但在这两种情况下都是./testfile
空的。
/dev/stdout
是每个进程自己的 STDOUT 文件描述符,当然不能工作。
可能有人知道如何实现这个或类似的替代方案?
初步说明
在这个答案中,我将术语“tty”用于任何终端设备,通常用于pts。
简单的方法
您可以使用一些工具来捕获命令的输出,同时仍为其提供 tty:
unbuffer
、script
、expect
、screen
、tmux
、ssh
,可能还有其他工具。对于单个外部命令,这相对容易。拿
ls --color=auto
。这次尝试:正如您所注意到的,“遭受上述问题”。但是这个:
很好地打印彩色和列式输出并将完整副本存储在
./log
. 这样做也是如此:尽管在这种情况下文件中会有页眉和页脚,但您可能喜欢也可能不喜欢它。
我不会详细说明
expect
,screen
或tmux
. 作为最后的手段(当没有其他工具可用时),可以ssh
在设置从本地主机到自身的无密码 SSH 访问后使用。像这样的东西:(
${var@Q}
扩展为var
可以作为输入重用的格式引用的值;在这里完美。运行的shellssh
必须是Bash,语法不可移植。)unbuffer
似乎是最简单的解决方案。顾名思义,它的主要目的是禁用缓冲,但它确实创建了一个伪终端。并发症
您还希望能够从 shell 函数捕获输出,而不会失去与解释脚本的主 shell 的连接。为此,该函数必须在主 shell 中运行,不能使用上述使用运行某些外部命令的工具的简单方法,除非外部命令是整个脚本:
显然,这个解决方案不是脚本所固有的。我猜你想简单地运行
./the-script
并捕获输出到终端的输出。所以脚本需要以某种方式为自己创建一个“可捕获”的 tty。这是棘手的部分。可能的解决方案
一个可能的解决方案是运行
并将主 shell 的文件描述符
1
和(可选)重定向2
到为something
.something
应该静静地坐在那里,(几乎)什么都不做。优点:
unbuffer … | tee … &
文件描述符并使用文件描述符来将脚本不同部分的输出记录到不同的文件中。缺点:
该脚本应该
kill
unbuffer
或something
何时不再需要记录。当它正常退出或因为有信号时,它应该这样做。如果它被强行杀死,那么它将无法做到这一点。也许something
应该定期检查主进程是否仍然存在并最终退出。有一个很好的解决方案flock
(见下文)。something
需要以某种方式将其 tty 报告给主 shell。仅打印输出tty
是可能的,然后主外壳将./log
独立打开并检索信息。在此之后,它只是文件中的垃圾(以及原始终端屏幕上)。该脚本可以截断文件,这仅适用于tee -a
(因为tee -a
vs在我的这个答案中tee
就像>>
vs )。最好通过单独的通道传递信息:临时文件或仅为此创建的命名 fifo。>
something
概念证明
以下代码需要
unbuffer
与expect
(in Debian:expect
package) 和flock
(in Debian:util-linux
package) 相关联。笔记:
save-stdout-stderr
并restore-stdout-stderr
使用硬编码值7
和8
. 您不应该将这些描述符用于其他任何事情。如果需要,重建此功能。create-logging-tty 21 ./log
是一个创建文件描述符21
(任意数字)的请求,该文件描述符将是一个记录到./log
(任意路径名)的 tty。该函数必须从主 shell(而不是子 shell)调用,因为它应该为主 shell 创建一个文件描述符。create-logging-tty
用于eval
创建具有请求编号的文件描述符。eval
可能是邪恶的,但在这里它是安全的,除非您传递一些不幸(或流氓)的 shell 代码而不是数字。该函数不验证其参数是否为数字。确保它是(或添加适当的测试)是您的工作。一般来说,示例中没有错误处理,所以也许你想添加一些。有时
return 1
函数无法通过 fifo 获取到新创建的 tty 的路径;函数的退出状态仍然没有在主代码中处理。自行修复此问题及更多内容。[ -t 21 ]
特别是,在将任何内容重定向到它之前,您可能想要测试所需的描述符是否真的导致了 tty ( )。create-logging-tty
使用该{variable}<>…
语法创建一个临时文件描述符,其中 shell 为其选择一个未使用的数字(10 或更大)并将该数字分配给variable
. 为了确保这不会完全偶然地获取请求的数字,该函数首先使用请求的数字创建一个文件描述符,然后才知道描述符最终应该指向的 tty。实际上,您可以请求任何合理的数字,并且函数的内部不会与任何东西发生冲突。如果您的整个脚本使用
{variable}<>…
或类似的语法,那么您可能不喜欢像 21 这样的硬编码数字的想法。这很容易解决:Inside
unbuffer
tty
(command) 用于获取 tty 提供的路径unbuffer
。正式tty
报告它的标准输入,我们更想知道它的标准输出。没关系,因为它们都指向同一个 tty。由于
flock
没有必要杀死unbuffer
它或它产生的外壳。这是它的工作原理:save-stdout-stderr
锁定它创建的fifo,它为此使用fifo的开放描述符。笔记:bash
解释整个脚本的进程 () 持有锁。unbuffer
要做的事情。unbuffer
通过 fifo 报告它的 tty,然后它尝试使用自己的文件描述符 3 锁定 fifo。关键是flock
阻塞,直到它获得锁定。flock
下unbuffer
不再被阻止。执行到第二个flock
尝试锁定 tty 和块。destroy-logging-tty
.flock
下unbuffer
才会解除阻塞。那里的外壳退出(自动释放它的锁),unbuffer
销毁 tty 并退出,tee
退出。无需维护。如果我们没有锁定 fifo,而是让 shell
unbuffer
立即锁定 tty,它可能会在主 shell 之前获得锁定,因此它会立即终止。主 shell 在知道它是什么之前无法锁定 tty。通过使用另一个锁和正确的锁定和解锁顺序,我们可以确保unbuffer
只有在主 shell 使用 tty 完成后才退出。最大的优势是:如果主 shell
SIGKILL
在运行之前因任何原因(包括)退出,destroy-logging-tty
那么内核无论如何都会释放进程持有的所有锁。这意味着unbuffer
最终将终止,不会有陈旧的进程。您可能想知道
tty
写入 fifo 是否会阻塞,直到函数从中读取。嗯,打开fifo阅读就够了。即使从未读取过fifo,像我们这样的写入过程也tty
将被允许在阻塞之前向其写入几个(千)字节。fifo 在主 shell 中打开以进行读取,但即使它过早退出,内部的 shellunubffer
也刚刚打开了 fifo 以进行写入和读取。这不应该阻塞。如果主 shell 在不幸的时刻被终止,唯一剩下的可能是 fifo 及其目录。您可以抑制或捕获某些信号,直到不幸的时刻过去;或者你可以
trap … EXIT
从陷阱内清洁。仍然存在SIGKILL
脚本无法执行任何操作的情况(例如)。我用交互式测试了这个解决方案,
mc
它基本上可以工作。期望./log
此类应用程序的输出 ( ) 包含许多控制序列。pv -qL 400 log
例如,可以在不太小的终端中重放日志。在我的测试中,它从其控制终端(ei 主终端,而不是 from )
mc
做出反应,并重新绘制了它的窗口,但它使用来自终端的大小作为其输出(来自 的那个)并且它从未改变。SIGWINCH
unbuffer
unbuffer
即使对尺寸
unbuffer
做出反应SIGWINCH
或以其他方式被迫更新尺寸,也可能为时已晚,mc
可能已经读取了旧尺寸。似乎这样的更新无论如何都不会发生。一个简单的解决方法是限制自己调整终端的大小。更广泛的问题
调整大小的问题
mc
是因为一个更广泛的问题。你写了:当有另一个 tty 的输出被记录并打印到原始控制台时,上述或类似的解决方案肯定不是“直接”的。
mc
如果直接打印到原始终端,将正确更新其大小。通常,您不能直接打印到终端并记录终端接收到的内容,除非终端本身支持记录。由
screen
或tmux
可以执行此操作的伪终端,您可以在脚本中以编程方式设置它们。一些带有 GUI 的终端仿真器可能允许您转储他们收到的内容,您需要通过 GUI 配置它们。关键是您需要一个具有该功能的终端。在“错误”终端中运行脚本,您无法以这种方式登录(您可以使用reptyr
“移动”到另一个终端)。该脚本可以像我们的脚本一样重新路由其输出,但这不是“直接”的。或者…有一些方法可以窥探 tty(示例)。也许你会找到适合你需要的东西。通常这种窥探需要提升访问权限,即使您想窥探您可以读取和写入的 tty。