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
    • 最新
    • 标签
主页 / unix / 问题 / 760143
Accepted
Stéphane Chazelas
Stéphane Chazelas
Asked: 2023-10-29 23:20:48 +0800 CST2023-10-29 23:20:48 +0800 CST 2023-10-29 23:20:48 +0800 CST

为什么 unbuffer -p 会破坏其输入?

  • 772
$ seq 10 | unbuffer -p od -vtc
0000000   1  \n   2  \n   3  \n   4  \n   5  \n   6  \n   7  \n   8  \n

9去了哪里10?

$ printf '\r' | unbuffer -p od -An -w1 -vtc
  \n

为什么\r改为\n?

$ : | unbuffer -p printf '\n' | od -An -w1 -vtc
  \r
  \n
$ unbuffer -p printf '\n' | od -An -w1 -vtc
  \r
      \n

搞什么?

$ printf foo | unbuffer -p cat
$

为什么没有输出(并且延迟一秒)?

$ printf '\1\2\3foo bar\n'  | unbuffer -p od -An -w1 -vtc
$

为什么没有输出?

$ (printf '\23'; seq 10000) | unbuffer -p cat

为什么它挂起并且没有输出?

$ unbuffer -p sleep 10

为什么我看不到我输入的内容(为什么即使我sleep没有阅读它也会被丢弃)?

顺便说一下,还有:

$ echo test | unbuffer -p grep foo && echo found foo
found foo

为什么grep找到foo但没有打印包含它的行?

$ unbuffer -p ls /x 2> /dev/null
ls: cannot access '/x': No such file or directory

为什么错误没有转到/dev/null?

另请参阅取消缓冲将所有字符转换为响铃?

$ echo ${(l[10000][foo])} | unbuffer -p cat | wc -c
4095

那是:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux trixie/sid
Release:        n/a
Codename:       trixie
$ uname -rsm
Linux 6.5.0-3-amd64 x86_64
$ expect -c 'puts "expect [package require Expect] tcl [info patchlevel]"'
expect 5.45.4 tcl 8.6.13
$ /proc/self/exe --version
zsh 5.9 (x86_64-debian-linux-gnu)

在 Ubuntu 22.04 或 FreeBSD 12.4-RELEASE-p5 上也是如此(除了od命令必须在那里进行调整,我得到 2321(那里的所有 BEL 字符)而不是上面的 4095)。

expect
  • 1 1 个回答
  • 1252 Views

1 个回答

  • Voted
  1. Best Answer
    Stéphane Chazelas
    2023-10-29T23:20:48+08:002023-10-29T23:20:48+08:00

    unbuffer是一个工具,用于禁用某些命令在其输出未发送到终端设备时执行的缓冲。

    当它们的输出发送到终端设备时,命令假设有一个实际用户正在主动查看输出,因此它们会在输出可用时立即发送它。嗯,不完全是,他们基于行发送,即一旦准备好输出就发送完整的行。

    当它不发送到终端设备时,例如当 stdout 是常规文件或管道时,作为优化,它们会以块的形式发送它。这意味着更少的write()s,并且在管道的情况下意味着另一端的读取器不需要经常被唤醒,这意味着更少的上下文切换。

    然而,这意味着:

    cmd | other-cmd
    

    在终端中运行,其中other-cmd有某种过滤/转换命令,other-cmd的 stdout 是行缓冲的,但cmds 是全缓冲的,这意味着交互式用户不会尽快看到 的输出cmd(由 转换)other-cmd因为它是可用的,但延迟并且是大批量的。

    unbuffer cmd | other-cmd
    

    有帮助,因为它恢复了基于行的缓冲,cmd即使它的标准输出将进入管道。

    为此,它cmd从伪终端开始,并将来自该伪终端的内容转发到管道。因此cmd认为它再次与用户交谈并进行行缓冲。

    unbuffer实际上是写在expect. 它是源代码中的示例脚本expect,通常包含在expect操作系统提供的包中。

    expect是一种用于使用伪终端与终端应用程序执行自动交互的工具,因此该unbuffer命令在expect. 开玩笑地说,其手册页的BUGS部分有:手册页比程序长。事实上,该计划只是:unbuffer

    #!/bin/sh
    # -*- tcl -*-
    # The next line is executed by /bin/sh, but not tcl \
    exec tclsh8.6 "$0" ${1+"$@"}
    
    package require Expect
    
    
    # -*- tcl -*-
    # Description: unbuffer stdout of a program
    # Author: Don Libes, NIST
    
    if {[string compare [lindex $argv 0] "-p"] == 0} {
        # pipeline
        set stty_init "-echo"
        eval [list spawn -noecho] [lrange $argv 1 end]
        close_on_eof -i $user_spawn_id 0
        interact {
            eof {
                # flush remaining output from child
                expect -timeout 1 -re .+
                return
            }
        }
    } else {
        set stty_init "-opost"
        set timeout -1
        eval [list spawn -noecho] $argv
        expect
        exit [lindex [wait] 3]
    }
    

    正如您所看到的以及手册页所确认的,unbuffer还支持一个-p选项。

    在 中unbuffer cmd,伪终端不仅连接到 cmd 的 stdout,还连接到它的 stdin 和 stderr(记住expect是一个旨在与命令交互的工具):

    $ tty; unbuffer readlink /proc/self/fd/{0..2}
    /dev/pts/14
    /dev/pts/15
    /dev/pts/15
    /dev/pts/15
    

    这解释了为什么unbuffer ls /x 2> /dev/null没有将错误发送到/dev/null,stderr 与 stdout 合并。

    现在,unbuffer不从其自己的标准输入读取任何内容,也不向 的标准输入发送任何内容cmd。

    这意味着A | unbuffer cmd | B行不通。

    这就是-p(for pipe) 选项的用武之地。如代码中所示, with -p,unbuffer使用interact而不是expect作为处理来自不同通道的数据的活动循环。

    仅使用该expect语句,expect(程序/TCL 库)读取来自伪终端的内容(例如cmd通过其 stdout 或 stderr 在从机端写入的内容),然后将其发送到自己的 stdout。

    使用interact,expect不仅可以:

    • 将从自己的标准输入读取的内容发送到伪终端(以便cmd可以在那里读取)
    • 另外,如果unbuffer的 stdin 恰好是终端设备,interact则将其置于raw本地echo禁用模式。

    这很好,因为A | unbuffer -p cmd | B,A的输出可以被读取为输入cmd,但意味着以下几点:

    • unbuffer使用 来配置内部伪终端set stty_init "-echo",但不在raw模式下。特别是,(( ) // isig的处理)、(流量控制,/ ( ))不会被禁用。当输入是终端设备时(这就是s的使用方式,而不是),这很好,因为主机设备被置于模式下,这意味着处理从主机终端转移到嵌入式伪终端终端,除了这两个终端都被禁用,所以你看不到你输入的内容。但是,当它不是终端设备时,这意味着例如输入中的任何 0x3 字节 ( )(如处理^C\3^Z^\ixon^Q^S\23expectinteractunbufferrawecho^Cprintf '\3') 触发 SIGINT 并终止命令,任何 0x19 字节 ( printf '\23') 都会停止流程。icrnl未被禁用解释了为什么\rs 更改为\ns。

    • 它不会做stty -opost它没有的情况下所做的事情-p。这解释了为什么 的\n输出cmd被更改为\r\n. 当输入是终端设备时,它将该设备放入 中raw,因此使用opost禁用这一事实解释了当 输出的换行符od未转换为时,终端输出被破坏\r\n。

    • cmd内部伪终端仍然启用行编辑器,因此除非有来自输入的\r或字符,否则不会发送任何内容,这解释了为什么不打印任何内容。\nprintf foo | unbuffer -p cat

      由于行编辑器对可编辑行的大小有限制(我的系统(Linux)上为 4095, FreeBSD 上tty 速度 1 的五分之一),因此您最终会遇到Unbuffer 转换所有字符的问题响铃?:当您尝试在哑应用程序(例如 )中在键盘上输入过长的行时,会发生同样的情况cat。在 Linux 上,第 4094个之后的所有字符都将被忽略,但\n会被接受并提交该行;在 FreeBSD 上,输入 38400/5 个字符后,任何多余的字符都会被拒绝(甚至\n),并导致 BEL 被发送到终端²。这解释了为什么你在那里得到 2321 BEL (10001 - 38400/5)。

    • 伪终端设备的 EOF 处理很笨重。当 的 stdin 上看到 EOF 时unbuffer,它无法将该信息转发到cmd. 因此seq 10 | od -vtc,在seq终止后,od仍在等待来自伪终端的更多输入,而这些输入永远不会到来。相反,到那时,一切都被拆除并被od杀死(手册页确实提到了这个限制)。

    unbuffer就其自身目的而言,如果将嵌入式伪终端置于raw -echo模式下并保留主机终端设备(如果有),效果会更好。然而expect并不真正支持这种操作模式,它不是为此设计的。

    现在,如果unbuffer是关于取消缓冲标准输出,那么它没有理由接触标准输入和标准错误。

    我们实际上可以通过以下方式解决这个问题:

    unbuffer() {
      command unbuffer sh -c 4<&0 5>&2 '
        exec <&4 4<&- 2>&5 5>&- "$@"' sh "$@"
    }
    

    它用于sh恢复原始的 stdin 和 stderr(由调用 shell 通过 fds 4 和 5 传递;不使用 fd 3,就像在expect内部显式使用该 fd 3 一样)。

    然后:

    $ echo test | unbuffer readlink /proc/self/fd/{0..2} 2> /dev/null | cat
    pipe:[184479]
    /dev/pts/16
    /dev/null
    

    只有 stdout 进入伪终端以进行无缓冲。

    所有其他问题都消失了:

    $ unbuffer ls /x 2> /dev/null
    $ printf '\r'  | unbuffer od -An -w1 -vtc
      \r
    $ : | unbuffer printf '\n' | od -An -w1 -vtc
      \n
    $ unbuffer printf '\n' | od -An -w1 -vtc
      \n
    $ printf foo | unbuffer cat
    foo
    $ printf '\1\2\3foo bar\n' | unbuffer od -An -w1 -vtc
     001
     002
     003
       f
       o
       o
    
       b
       a
       r
      \n
    $ (printf '\23'; seq 10000) | unbuffer cat -vte | head
    ^S1$
    2$
    3$
    4$
    5$
    6$
    7$
    8$
    9$
    10$
    $ unbuffer sleep 10
    I see what I type
    $ I see what I type
    zsh: command not found: I
    $ echo test | unbuffer grep foo || echo not found
    not found
    $ echo ${(l[10000][foo])} | unbuffer cat | wc -c
    10001
    

    当您需要的只是通过伪终端进行标准输出时,安装expect(需要 TCL 解释器)似乎有点矫枉过正。cmd

    socat也可以这样做:

    $ echo test | socat -u system:'readlink /proc/self/fd/[0-2]; wc -c',pty,raw - 2> /dev/null | cat
    pipe:[187759]
    /dev/pts/17
    /dev/null
    5
    

    (它记录失败退出状态,但不会传播命令的退出状态)。

    shellzsh甚至内置了对伪 ttys 的支持,并且unbuffer可以轻松地编写一个函数:

    zmodload zsh/zpty
    zmodload zsh/zselect
    unbuffer() {
      {
        return "$(
          exec 6>&1 >&5 5>&-
          # here fds go:
          #  0,3: orig stdin
          #    1: orig stdout
          #  2,4: orig stderr
          #    5: closed
          #    6: to return argument
          zpty -b unbuffer '
            stty raw
            exec <&3 3<&- 2>&4 4>&-
            # here fds go:
            #     0: orig stdin
            #     1: pseudo unbuffering tty
            #     2: orig stderr
            # 3,4,5: closed
            #     6: to return argument
            "$@" 6>&-
            echo "$?" >&6 
          '
          fd=$REPLY
          until
            zselect -r $fd
            zpty -r unbuffer
            (( $? == 2 ))
          do
            continue
          done
        )"
      } 3<&0 4>&2 5>&1
    }
    

    请注意,所有这些最终都会在新终端中运行,除了新会话中的socat方法(除非您使用ctty和选项)。setid因此,现在如果这些“fixed”unbuffer在主机终端会话的后台启动,则cmd不会停止从主机终端读取数据。例如,unbuffer cat&最终会从您的终端读取后台作业,从而造成严重破坏。


    1 上限为 65536。伪终端的速度无关紧要,但必须有一个广告,我发现在我测试的 FreeBSD 系统上默认情况下它是 38400 。由于速度是从控制终端的速度复制的expect,因此可以在调用之前执行stty speed 115200(最大值AFAICT)unbuffer以扩大该缓冲区。但您可能会发现您仍然没有获得完整的 10000 字符大行。这在驱动程序代码中进行了解释。您会发现unbuffer -p cat仅返回 4096 字节,因为这cat在第一次read()调用中请求了同样多的字节,并且 tty 驱动程序从输入行返回了同样多的字节,但丢弃了其余的(!)。如果你替换为unbuffer -p dd bs=65536,您将获得完整的行(最多 115200/5 字节)。

    ² 您可以通过在脚本中替换为来避免这些 BEL set stty_init "-echo",set stty_init "-echo -imaxbel"但这unbuffer不会帮助您获取数据。

    • 24

相关问题

  • 期望 - 如果命令失败,如何以状态码 1 退出?

  • 如何使期望脚本期望多个文本中的任何一个

  • 生成 bash 脚本时,Expect 脚本返回错误。为什么?[复制]

  • Expect、Command、管道和 Gzip

  • 用期望定义输入数字

Sidebar

Stats

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

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

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

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve