【问题标题】:How to trick an application into thinking its stdout is a terminal, not a pipe如何欺骗应用程序认为其标准输出是终端,而不是管道
【发布时间】:2010-11-26 22:11:58
【问题描述】:

我正在尝试与“Detect if stdin is a terminal or pipe?”相反。

我正在运行一个正在更改其输出格式的应用程序,因为它检测到 STDOUT 上的管道,并且我希望它认为它是一个交互式终端,以便在重定向时获得相同的输出。

我在想将它包装在 expect 脚本中或在 PHP 中使用 proc_open() 会做到这一点,但事实并非如此。

有什么想法吗?

【问题讨论】:

  • empty.sf.net 有帮助吗?
  • @ephemient :应该是一个答案。顺便说一句,很棒的实用程序...
  • 这个问题谈到了标准输出,但标题提到了标准输入。我认为标题是错误的。

标签: bash terminal pipe stdin


【解决方案1】:

啊哈!

script 命令执行我们想要的操作...

script --return --quiet -c "[executable string]" /dev/null

成功了!

Usage:
 script [options] [file]

Make a typescript of a terminal session.

Options:
 -a, --append                  append the output
 -c, --command <command>       run command rather than interactive shell
 -e, --return                  return exit code of the child process
 -f, --flush                   run flush after each write
     --force                   use output file even when it is a link
 -q, --quiet                   be quiet
 -t[<file>], --timing[=<file>] output timing data to stderr or to FILE
 -h, --help                    display this help
 -V, --version                 display version

【讨论】:

  • +1:只是偶然发现了一个做静态初始化的库的问题。 Fedora 12 中最近的一项更改使得当 exe 启动时不在 tty 中时该 init 失败。你的把戏完美无缺。我更喜欢它而不是 unbuffer 因为脚本是默认安装的!
  • 如果你想通过管道将它转换成交互的东西,比如less -R,终端输入到less -R,那么你需要一些额外的技巧。例如,我想要一个彩色版本的git status | less。您需要将-R 传递给less 以使其尊重颜色,并且您需要使用script 来获取git status 以输出颜色。但我们不希望script 保留键盘的所有权,我们希望将其交给less。所以我现在使用它并且效果很好:0&lt;&amp;- script -qfc "git status" /dev/null | less -R。前几个字符关闭这个命令的标准输入。
  • 注意:如果组件检查交互性正在查看 $- shell 变量中的“i”,这将不起作用。
  • 这太棒了。对于在 Wine 中运行的可执行文件中嵌入 Python 库的极其罕见的用例,我需要它。当我在终端中运行时它可以工作,但是当我运行我创建的 .desktop 文件时,它总是会崩溃,因为Py_Initialize 没有看到正确的标准输入/标准错误。
  • 是否有一种与操作系统无关的方式来执行此操作?我喜欢 macOS 上的脚本命令,因为您不必将命令括在引号中。该脚本运行并将输出发送到在提供的文件中重复的 tty,但我似乎无法让 linux 版本以相同的方式运行......我可能做错了什么。那么在 macOS 上等效的 linux 脚本命令是什么:script -q -t 0 tmp.out perl -e 'print "Test\n"'Testcat tmp.outTest
【解决方案2】:

基于Chris' solution,我想出了如下的小辅助函数:

faketty() {
    script -qfc "$(printf "%q " "$@")" /dev/null
}

看起来古怪的 printf 是正确扩展 $@ 中脚本参数的必要条件,同时保护命令中可能引用的部分(参见下面的示例)。

用法:

faketty <command> <args>

示例:

$ python -c "import sys; print sys.stdout.isatty()"
True
$ python -c "import sys; print sys.stdout.isatty()" | cat
False
$ faketty python -c "import sys; print sys.stdout.isatty()" | cat
True

【讨论】:

  • 如果您的script 版本有,您可能希望使用--return 选项来保留子进程的退出代码。
  • 我建议把这个函数改成这样:function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } 否则,在很多情况下,每次运行命令时都会创建一个名为typescript 的文件。
  • 似乎无法在 MacOS 上运行,我收到 script: illegal option -- f
【解决方案3】:

Expect 附带的unbuffer 脚本应该处理这个问题。如果不是,则应用程序可能正在查看其输出所连接的内容以外的其他内容,例如。 TERM 环境变量设置为什么。

【讨论】:

    【解决方案4】:

    参考之前的答案,在 Mac OS X 上,“脚本”可以像下面这样使用...

    script -q /dev/null commands...
    

    但是,因为它可能会在标准输出上将“\n”替换为“\r\n”,所以您可能还需要这样的脚本:

    script -q /dev/null commands... | perl -pe 's/\r\n/\n/g'
    

    如果这些命令之间有一些管道,则需要刷新标准输出。例如:

    script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' |  perl -pe 's/\r\n/\n/g'
    

    【讨论】:

    • 感谢 OS X 语法,但是,根据您的 Perl 语句判断,您的意思似乎是说它将“\r\n”的实例更改为“\n”,而不是另一个绕路,对吗?
    【解决方案5】:

    我不知道它是否可以从 PHP 中实现,但如果您确实需要子进程来查看 TTY,您可以创建一个PTY

    在 C 中:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sysexits.h>
    #include <unistd.h>
    #include <pty.h>
    
    int main(int argc, char **argv) {
        int master;
        struct winsize win = {
            .ws_col = 80, .ws_row = 24,
            .ws_xpixel = 480, .ws_ypixel = 192,
        };
        pid_t child;
    
        if (argc < 2) {
            printf("Usage: %s cmd [args...]\n", argv[0]);
            exit(EX_USAGE);
        }
    
        child = forkpty(&master, NULL, NULL, &win);
        if (child == -1) {
            perror("forkpty failed");
            exit(EX_OSERR);
        }
        if (child == 0) {
            execvp(argv[1], argv + 1);
            perror("exec failed");
            exit(EX_OSERR);
        }
    
        /* now the child is attached to a real pseudo-TTY instead of a pipe,
         * while the parent can use "master" much like a normal pipe */
    }
    

    我实际上的印象是expect 本身确实创建了一个 PTY。

    【讨论】:

    • 你知道如何在 mac os x 上运行 nettop 作为子进程吗?我想在我的应用程序中获取 nettop 的输出。我尝试使用 forkpty 但仍然无法成功运行 nettop。
    【解决方案6】:

    更新@A-Ron 的回答 a) 在 Linux 和 MacO 上工作 b) 间接传播状态码(因为 MacOs script 不支持)

    faketty () {
      # Create a temporary file for storing the status code
      tmp=$(mktemp)
    
      # Ensure it worked or fail with status 99
      [ "$tmp" ] || return 99
    
      # Produce a script that runs the command provided to faketty as
      # arguments and stores the status code in the temporary file
      cmd="$(printf '%q ' "$@")"'; echo $? > '$tmp
    
      # Run the script through /bin/sh with fake tty
      if [ "$(uname)" = "Darwin" ]; then
        # MacOS
        script -Fq /dev/null /bin/sh -c "$cmd"
      else
        script -qfc "/bin/sh -c $(printf "%q " "$cmd")" /dev/null
      fi
    
      # Ensure that the status code was written to the temporary file or
      # fail with status 99
      [ -s $tmp ] || return 99
    
      # Collect the status code from the temporary file
      err=$(cat $tmp)
    
      # Remove the temporary file
      rm -f $tmp
    
      # Return the status code
      return $err
    }
    

    例子:

    $ faketty false ; echo $?
    1
    
    $ faketty echo '$HOME' ; echo $?
    $HOME
    0
    
    embedded_example () {
      faketty perl -e 'sleep(5); print "Hello  world\n"; exit(3);' > LOGFILE 2>&1 </dev/null &
      pid=$!
    
      # do something else
      echo 0..
      sleep 2
      echo 2..
    
      echo wait
      wait $pid
      status=$?
      cat LOGFILE
      echo Exit status: $status
    }
    
    $ embedded_example
    0..
    2..
    wait
    Hello  world
    Exit status: 3
    

    【讨论】:

    • 我使用了非标准的SHELL,所以这个功能在我的(Linux)环境中失败了。 script 命令使用SHELL 运行/bin/sh -c ...,后者运行$cmd。通过显式设置SHELL=/bin/sh,似乎可以去掉这三个阶段之一。我用更简单的 SHELL=/bin/sh script -qfc "$cmd" /dev/null 替换了 Linux script 行,它的功能似乎相同。
    【解决方案7】:

    对具体答案发表评论太新了,但我想我会跟进上面 ingomueller-net 发布的 faketty 函数,因为它最近帮助了我。

    我发现这是在创建一个我不需要/不需要的 typescript 文件,因此我将 /dev/null 添加为脚本目标文件:

    function faketty { script -qfc "$(printf "%q " "$@")" /dev/null ; }

    【讨论】:

      【解决方案8】:

      《UNIX环境下的高级编程,第二版》一书的示例代码中还包含一个pty程序!

      这是在 Mac OS X 上编译 pty 的方法:

      man 4 pty  #  pty -- pseudo terminal driver
      
      open http://en.wikipedia.org/wiki/Pseudo_terminal
      
      # Advanced Programming in the UNIX Environment, Second Edition
      open http://www.apuebook.com
      
      cd ~/Desktop
      
      curl -L -O http://www.apuebook.com/src.tar.gz
      
      tar -xzf src.tar.gz
      
      cd apue.2e
      
      wkdir="${HOME}/Desktop/apue.2e"
      
      sed -E -i "" "s|^WKDIR=.*|WKDIR=${wkdir}|" ~/Desktop/apue.2e/Make.defines.macos
      
      echo '#undef _POSIX_C_SOURCE' >> ~/Desktop/apue.2e/include/apue.h
      
      str='#include   <sys/select.h>'
      printf '%s\n' H 1i "$str" . wq | ed -s calld/loop.c
      
      str='
      #undef _POSIX_C_SOURCE
      #include <sys/types.h>
      '
      printf '%s\n' H 1i "$str" . wq | ed -s file/devrdev.c
      
      str='
      #include <sys/signal.h>
      #include <sys/ioctl.h>
      '
      printf '%s\n' H 1i "$str" . wq | ed -s termios/winch.c
      
      make
      
      ~/Desktop/apue.2e/pty/pty ls -ld *
      

      【讨论】:

      • 也很奇怪的错误:快速错误:未知域:coden-ps.joyent.com。请检查该域是否已添加到服务中。
      【解决方案9】:

      我在 Linux 上运行 shellcheck &lt;file&gt; | less 时试图获得颜色,所以我尝试了上面的答案,但它们会产生这种奇怪的效果,即文本与应有的位置水平偏移:

      In ./all/update.sh line 6:
                                for repo in $(cat repos); do
                                                                        ^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.
      

      (对于不熟悉shellcheck的人来说,警告的那一行应该和问题所在的位置一致。)

      为了使上述答案与 shellcheck 一起使用,我尝试了 cmets 中的一种选项:

      faketty() {                       
          0</dev/null script -qfc "$(printf "%q " "$@")" /dev/null
      }
      

      这行得通。我还添加了--return 并使用了长选项,以使这个命令不那么难以理解:

      faketty() {                       
          0</dev/null script --quiet --flush --return --command "$(printf "%q " "$@")" /dev/null
      }
      

      适用于 Bash 和 Zsh。

      【讨论】:

      • 很确定这在 bsd 系统上的 bash 和 zsh 中都不起作用。
      • @oligofren 我鼓励你提交一个答案,然后。
      • 不需要,另一个答案涵盖了这一点。我只是说您的答案的主要驱动因素是依赖于操作系统的,无论外壳如何。我可以进行编辑,以澄清:)
      猜你喜欢
      • 1970-01-01
      • 2014-10-20
      • 2016-04-27
      • 2021-02-16
      • 1970-01-01
      相关资源
      最近更新 更多