从另一个命令启动的 ssh-add 捕获输出

问题描述

这是我的问题的完整版本。我将所有这些细节都包括在内,以防我的预感是错误的,但您可能想跳到下面的 tl;dr。

我正在尝试编写一个运行任意命令并捕获是否有任何输出打印到终端的函数。我不想干扰正在打印的输出。如果这是一个相关的并发症(可能不是),我想在命令的退出代码上进行分支。

这是我所拥有的:

function run_and_inspect {
    # this subshell ensures the stdout of ${@} is printed and captured
    if output=$(
        set -o pipefail
        "${@}" | tee /dev/tty
    ); then
        did_cmd_work="yes"
    else
        did_cmd_work="no"
    fi

    if [ -z "$output" ]; then
        was_there_output="no"
    else
        was_there_output="yes"
    fi

    echo "output?" $was_there_output
}

通常这很好用:

$ run_and_inspect true
output? no

$ run_and_inspect "echo hello"
hello
output? yes

但我发现了一个有问题的命令:

git pull | grep -v 'Already up to date.'

如果没有什么可以拉,这个管道通常不会产生任何输出。但是,如果 ssh-add 需要提示输入密码,则输出。只是没有被 run_and_inspect 注意到:

function git_pull_quiet {
    git pull | grep -v 'Already up to date.'
}

$ run_and_inspect git_pull_quiet
Enter passphrase for key '/home/foo/.ssh/id_ed25519':
output? no

输出到我的终端。我认为问题是它不是来自 git pull 管道的标准输出,这是 run_and_inspect 所知道的。它从哪里来的?我该如何解决?我也尝试过重定向 stderr(即 git pull 2>&1),但没有成功。有没有办法直接监控/dev/tty?

tl;dr(我认为!)

认为这个问题归结为:为什么 log.txt 中没有密码提示

$ git pull 2>&1 | tee log.txt
Enter passphrase for key '/home/foo/.ssh/id_ed25519':
Already up to date.
$ cat log.txt
Already up to date.

解决方法

为什么 log.txt 中没有密码提示?

提示是从 openssh load_identify_filereadpass.c read_passphrase() 打印出来的。该函数对 _PATH_TTY "/dev/tty" 执行 open(_PATH_TTY,然后对它执行 write()

输出直接显示到终端 /dev/tty,而不是标准流。

就像您对 tee /dev/tty 所做的一样,这意味着您的函数也将不起作用。更喜欢保留子程序的标准输出并保留缓冲:

if { tmp=$("$@" > >(tee >(cat >&3))); } 3>&1; then

有没有办法直接监控/dev/tty?

编写您自己的终端模拟器并在其中生成您的进程,然后解析该终端模拟器中的所有输入。 screentmux 之类的程序可能有用。

解决方法可能是下载 proot 并打开某个文件的文件描述符,然后使用符号链接到 /dev/tty/proc/self/fd/<that file descriptor> 文件创建一个 chroot 并使用 {{1 }} 在那个 chroot 里面。这个想法是该进程将看到 proot 文件被替换的 chroot 并将写入您的文件描述符而不是终端。