如何欺骗应用程序以使其标准输出是终端而不是管道


147

我正在尝试执行与“ 检测stdin是终端还是管道? ” 相反的操作。

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

我当时认为将其包装在expect脚本中或proc_open()在PHP中使用可以实现,但事实并非如此。

有什么想法吗?


8
是否empty.sf.net帮助?
短暂的

1
@ephemient:应该是一个答案。顺便说一句伟大的工具……
Neuro

问题是关于stdout的,但标题中提到了stdin。我认为标题是错误的。
Piotr Dobrogost

Answers:


177

啊哈!

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
+1:偶然发现一个使用静态初始化的库的问题。Fedora 12中的最新更改使该exe不在tty中时初始化失败。你的把戏很完美。我默认使用unbuffer作为脚本,因为它是默认安装的!
神经

script甚至可以在BusyBox中使用
支石墓

9
如果您希望将其通过管道传递到交互式内容中,例如less -R,终端输入将进入less -R,则您需要一些额外的技巧。例如,我想要彩色版本的git status | less。你需要传递-R到更小,以便它尊重的颜色,你需要使用script获得git status输出色彩。但是我们不想script保留键盘的所有权,我们希望它去less。因此,我现在使用它,并且效果很好:0<&- script -qfc "git status" /dev/null | less -R 。前几个字符为此命令关闭了stdin。
亚伦·麦克戴德

2
注意:在检查交互性的组件正在$-外壳变量中查找“ i”的情况下,此方法不起作用。
杰伊·泰勒

1
这真太了不起了。对于极罕见的用例,在Wine中运行的可执行文件中带有嵌入式Python库时,我需要使用它。当我在终端中运行时,它可以运行,但是当我运行.desktop文件时,它始终会崩溃,因为Py_Initialize没有看到正确的stdin / stderr。
Tatsh

60

根据克里斯的解决方案,我想到了以下小助手功能:

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

9
您可能想使用该--return选项(如果您的版本script具有该选项)来保留子进程的退出代码。
jwd

5
我建议像这样更改此功能: 在许多情况下,每次运行命令时都会创建一个function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } 名为的文件typescript
w0rp

1
我似乎无法在MacOS上使用,我知道 script: illegal option -- f
Alexander Mills

23

Expect附带的unbuffer脚本应该可以解决此问题。如果不是,则应用程序可能正在查看与其输出所连接的对象不同的对象,例如。TERM环境变量设置为什么。


17

我不知道它在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。
文斯·袁

16

参考先前的答案,在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'

1
感谢您使用OS X语法,但是从您的Perl语句来看,您似乎想说它会将“ \ r \ n”的实例更改为“ \ n”,不是相反的,对吗?
mklement0

8

太新了,无法对具体答案发表评论,但我想我应该对 faketty为时过早应该对上面的ingomueller-net发布功能进行跟进,因为它最近帮助了我。

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

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


3

更新@ A-Ron的答案a)在Linux和MacO上均可工作b)间接传播状态代码(因为MacO 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

2

我试图在运行时获取颜色shellcheck <file> | 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的上述答案,我尝试了注释中的一种选择:

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中使用。


1

“ 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 *

也是非常奇怪的错误:快速错误:未知域:codesnippets.joyent.com。请检查该域是否已添加到服务中。
i336_
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.