AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / computer / 问题 / 1603249
Accepted
MichaIng
MichaIng
Asked: 2020-11-19 13:36:05 +0800 CST2020-11-19 13:36:05 +0800 CST 2020-11-19 13:36:05 +0800 CST

无需重定向或管道即可读取控制台输出

  • 772

有没有一种方法可以读取命令的控制台输出而无需重定向或通过管道传输其 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 文件描述符,当然不能工作。

可能有人知道如何实现这个或类似的替代方案?

linux bash
  • 1 1 个回答
  • 1557 Views

1 个回答

  • Voted
  1. Best Answer
    Kamil Maciorowski
    2020-11-21T04:59:58+08:002020-11-21T04:59:58+08:00

    初步说明

    在这个答案中,我将术语“tty”用于任何终端设备,通常用于pts。


    简单的方法

    您可以使用一些工具来捕获命令的输出,同时仍为其提供 tty:unbuffer、script、expect、screen、tmux、ssh,可能还有其他工具。

    对于单个外部命令,这相对容易。拿ls --color=auto。这次尝试:

    ls --color=auto | tee ./log
    

    正如您所注意到的,“遭受上述问题”。但是这个:

    unbuffer ls --color=auto | tee ./log         # or tee -a
    

    很好地打印彩色和列式输出并将完整副本存储在./log. 这样做也是如此:

    script -qc 'ls --color=auto' ./log           # or script -a
    

    尽管在这种情况下文件中会有页眉和页脚,但您可能喜欢也可能不喜欢它。

    我不会详细说明expect,screen或tmux. 作为最后的手段(当没有其他工具可用时),可以ssh在设置从本地主机到自身的无密码 SSH 访问后使用。像这样的东西:

    ssh -tt localhost "cd ${PWD@Q} && ls --color=auto" | tee ./log
    

    (${var@Q}扩展为var可以作为输入重用的格式引用的值;在这里完美。运行的shellssh必须是Bash,语法不可移植。)

    unbuffer似乎是最简单的解决方案。顾名思义,它的主要目的是禁用缓冲,但它确实创建了一个伪终端。


    并发症

    您还希望能够从 shell 函数捕获输出,而不会失去与解释脚本的主 shell 的连接。为此,该函数必须在主 shell 中运行,不能使用上述使用运行某些外部命令的工具的简单方法,除非外部命令是整个脚本:

    unbuffer ./the-script | tee ./log
    

    显然,这个解决方案不是脚本所固有的。我猜你想简单地运行./the-script并捕获输出到终端的输出。所以脚本需要以某种方式为自己创建一个“可捕获”的 tty。这是棘手的部分。


    可能的解决方案

    一个可能的解决方案是运行

    unbuffer something | tee ./log &         # or tee -a
    

    并将主 shell 的文件描述符1和(可选)重定向2到为something. something应该静静地坐在那里,(几乎)什么都不做。

    优点:

    • 您可以将原始文件描述符保存为不同的数字,然后您可以随时通过将标准输入和标准输出重定向回原来的状态来停止记录。
    • 您可以运行多个unbuffer … | tee … &文件描述符并使用文件描述符来将脚本不同部分的输出记录到不同的文件中。
    • 您可以选择性地重定向任何单个命令的标准输出和/或标准错误。

    缺点:

    • 该脚本应该kill unbuffer或something何时不再需要记录。当它正常退出或因为有信号时,它应该这样做。如果它被强行杀死,那么它将无法做到这一点。也许something应该定期检查主进程是否仍然存在并最终退出。有一个很好的解决方案flock(见下文)。

    • something需要以某种方式将其 tty 报告给主 shell。仅打印输出tty是可能的,然后主外壳将./log独立打开并检索信息。在此之后,它只是文件中的垃圾(以及原始终端屏幕上)。该脚本可以截断文件,这仅适用于tee -a(因为tee -avs在我的这个答案中tee就像>>vs )。最好通过单独的通道传递信息:临时文件或仅为此创建的命名 fifo。>something


    概念证明

    以下代码需要unbuffer与expect(in Debian: expectpackage) 和flock(in Debian: util-linuxpackage) 相关联。

    #!/bin/bash
    
    save-stdout-stderr() {
       exec 7>&1 8>&2
    }
    
    restore-stdout-stderr() {
       exec 1>&7 2>&8 7>&- 8>&-
    }
    
    create-logging-tty() {
     # usage: create-logging-tty descriptor log
       local tmpdir tmpfifo tmpdesc tty descriptor log
       descriptor="$1"
       log="$2"
       tmpdir="$(mktemp -d)"
       tmpfifo="$tmpdir/fifo"
       mkfifo "$tmpfifo"
       eval 'exec '"$descriptor"'>/dev/null'
       exec {tmpdesc}<>"$tmpfifo"
       flock "$tmpdesc"
       unbuffer sh -c '
          exec 3<>"$1"
          tty >&3
          flock 3
          flock 2
       ' sh "$tmpfifo" | tee "$log" &
       if ! IFS= read -ru "$tmpdesc" -t 5 tty; then
          rm -r "$tmpdir"
          exec {descriptor}>&-
          flock -u "$tmpdesc"
          return 1
       fi
       rm -r "$tmpdir"
       eval 'exec '"$descriptor"'> "$tty"'
       flock "$descriptor"
       flock -u "$tmpdesc"
    }
    
    destroy-logging-tty() {
     # usage: destroy-logging-tty descriptor
       local descriptor
       descriptor="$1"
       flock -u "$descriptor"
       exec {descriptor}>&-
    }
    
    # here the actual script begins
    
    save-stdout-stderr
    
    echo "This won't be logged."
    
    create-logging-tty 21 ./log
    exec 1>&21 2>&21
    
    echo "This will be logged."
    
    # proof of concept
    ls --color=auto /dev
    
    restore-stdout-stderr
    destroy-logging-tty 21
    
    echo "This won't be logged."
    

    笔记:

    • 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 这样的硬编码数字的想法。这很容易解决:

      exec {foo}>/dev/null
      create-logging-tty "$foo" ./log
      exec 1>&"$foo" 2>&"$foo"
      …
      destroy-logging-tty "$foo"
      
    • Inside unbuffer tty(command) 用于获取 tty 提供的路径unbuffer。正式tty报告它的标准输入,我们更想知道它的标准输出。没关系,因为它们都指向同一个 tty。

    • 由于flock没有必要杀死unbuffer它或它产生的外壳。这是它的工作原理:

      1. save-stdout-stderr锁定它创建的fifo,它为此使用fifo的开放描述符。笔记:
        • 该函数在主 shell 中运行,因此实际上描述符是在主 shell 中打开的,因此bash解释整个脚本的进程 () 持有锁。
        • 锁不会阻止其他进程写入fifo。只有当他们想为自己锁定 fifo 时,它才会阻止他们。这就是运行的 shell 代码unbuffer要做的事情。
      2. 运行的 shell 代码unbuffer通过 fifo 报告它的 tty,然后它尝试使用自己的文件描述符 3 锁定 fifo。关键是flock阻塞,直到它获得锁定。
      3. 该函数读取有关 tty 的信息,创建请求的描述符并使用描述符锁定 tty。只有这样它才能解锁fifo。
      4. 第一flock下unbuffer不再被阻止。执行到第二个flock尝试锁定 tty 和块。
      5. 主脚本继续。当不再需要 tty 时,主 shell 通过destroy-logging-tty.
      6. 只有这样,第二个flock下unbuffer才会解除阻塞。那里的外壳退出(自动释放它的锁),unbuffer销毁 tty 并退出,tee退出。无需维护。

      如果我们没有锁定 fifo,而是让 shellunbuffer立即锁定 tty,它可能会在主 shell 之前获得锁定,因此它会立即终止。主 shell 在知道它是什么之前无法锁定 tty。通过使用另一个锁和正确的锁定和解锁顺序,我们可以确保unbuffer只有在主 shell 使用 tty 完成后才退出。

      最大的优势是:如果主 shellSIGKILL在运行之前因任何原因(包括)退出,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做出反应,并重新绘制了它的窗口,但它使用来自终端的大小作为其输出(来自 的那个)并且它从未改变。SIGWINCHunbufferunbuffer

      即使对尺寸unbuffer做出反应SIGWINCH或以其他方式被迫更新尺寸,也可能为时已晚,mc可能已经读取了旧尺寸。似乎这样的更新无论如何都不会发生。一个简单的解决方法是限制自己调整终端的大小。


    更广泛的问题

    调整大小的问题mc是因为一个更广泛的问题。你写了:

    我想要实现的是执行一个命令,以便将其输出直接打印到控制台 […]

    当有另一个 tty 的输出被记录并打印到原始控制台时,上述或类似的解决方案肯定不是“直接”的。mc如果直接打印到原始终端,将正确更新其大小。

    通常,您不能直接打印到终端并记录终端接收到的内容,除非终端本身支持记录。由screen或tmux可以执行此操作的伪终端,您可以在脚本中以编程方式设置它们。一些带有 GUI 的终端仿真器可能允许您转储他们收到的内容,您需要通过 GUI 配置它们。关键是您需要一个具有该功能的终端。在“错误”终端中运行脚本,您无法以这种方式登录(您可以使用reptyr“移动”到另一个终端)。该脚本可以像我们的脚本一样重新路由其输出,但这不是“直接”的。或者…

    有一些方法可以窥探 tty(示例)。也许你会找到适合你需要的东西。通常这种窥探需要提升访问权限,即使您想窥探您可以读取和写入的 tty。

    • 1

相关问题

  • Notify-发送窗口下出现的通知

  • 以 root 身份运行 docker 容器

  • 如何在域和 Linux 活动目录中启用指纹传感器

  • 如何在CentOS 7 中将Ctrl+C 永久更改为Ctrl+K?

  • 如何从 WSL 打开 office 文件

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何减少“vmmem”进程的消耗?

    • 11 个回答
  • Marko Smith

    从 Microsoft Stream 下载视频

    • 4 个回答
  • Marko Smith

    Google Chrome DevTools 无法解析 SourceMap:chrome-extension

    • 6 个回答
  • Marko Smith

    Windows 照片查看器因为内存不足而无法运行?

    • 5 个回答
  • Marko Smith

    支持结束后如何激活 WindowsXP?

    • 6 个回答
  • Marko Smith

    远程桌面间歇性冻结

    • 7 个回答
  • Marko Smith

    子网掩码 /32 是什么意思?

    • 6 个回答
  • Marko Smith

    鼠标指针在 Windows 中按下的箭头键上移动?

    • 1 个回答
  • Marko Smith

    VirtualBox 无法以 VERR_NEM_VM_CREATE_FAILED 启动

    • 8 个回答
  • Marko Smith

    应用程序不会出现在 MacBook 的摄像头和麦克风隐私设置中

    • 5 个回答
  • Martin Hope
    CiaranWelsh 如何减少“vmmem”进程的消耗? 2020-06-10 02:06:58 +0800 CST
  • Martin Hope
    Jim Windows 10 搜索未加载,显示空白窗口 2020-02-06 03:28:26 +0800 CST
  • Martin Hope
    v15 为什么通过电缆(同轴电缆)的千兆位/秒 Internet 连接不能像光纤一样提供对称速度? 2020-01-25 08:53:31 +0800 CST
  • Martin Hope
    fixer1234 “HTTPS Everywhere”仍然相关吗? 2019-10-27 18:06:25 +0800 CST
  • Martin Hope
    andre_ss6 远程桌面间歇性冻结 2019-09-11 12:56:40 +0800 CST
  • Martin Hope
    Riley Carney 为什么在 URL 后面加一个点会删除登录信息? 2019-08-06 10:59:24 +0800 CST
  • Martin Hope
    zdimension 鼠标指针在 Windows 中按下的箭头键上移动? 2019-08-04 06:39:57 +0800 CST
  • Martin Hope
    jonsca 我所有的 Firefox 附加组件突然被禁用了,我该如何重新启用它们? 2019-05-04 17:58:52 +0800 CST
  • Martin Hope
    MCK 是否可以使用文本创建二维码? 2019-04-02 06:32:14 +0800 CST
  • Martin Hope
    SoniEx2 更改 git init 默认分支名称 2019-04-01 06:16:56 +0800 CST

热门标签

windows-10 linux windows microsoft-excel networking ubuntu worksheet-function bash command-line hard-drive

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve