如何欺骗应用程序,使其认为自己的标准输出是一个终端,而不是一个管道

我在尝试做“ 检测 stdin 是终端还是管道?”的反义词。

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

我认为用 expect脚本包装它或者在 PHP 中使用 proc_open()就可以了,但是它没有。

有什么想法吗?

61285 次浏览

我不知道它是否可以从 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。

啊哈!

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

期待 应该附带的 解除缓冲脚本可以处理这个问题。如果没有,应用程序可能正在查看其输出连接到的东西以外的东西,例如,TERM 环境变量被设置为什么。

还有一个 pty 程序包含在书的样本代码“ UNIX环境高级编程,第二版”!

以下是如何在 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 *

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

script -q /dev/null commands...

但是,因为它可能在 stdout 上用“ r n”替换“ 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'

基于 Chris 的解决方案,我想出了以下小助手函数:

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

太新了以至于无法评论具体的答案,但是我想我应该跟进上面 inomueller-net 发布的 faketty函数,因为它最近帮助了我。

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

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

在 Linux 上运行 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.

(对于那些不熟悉 shell 检查的人来说,带有警告的行应该与问题所在的位置一致。)

为了得到以上使用 shell 的答案,我尝试了评论中的一个选项:

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 中工作。

更新@A-Ron 的回答 A)同时使用 Linux 和 MacO B)间接传播状态代码(因为 MacOsscript不支持)

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