0

Here's the full version of my question. I'm including all this detail in case my hunch is wrong, but you may want to skip to the tl;dr below.

I'm trying to write a function that runs an arbitrary command and also captures whether any output was printed to the terminal. I don't want to interfere with the output being printed. In case it's a relevant complication (probably not), I also want to branch on the exit code of the command.

Here's what I have:

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
}

Generally this works fine:

$ run_and_inspect true
output? no

$ run_and_inspect "echo hello"
hello
output? yes

But I've found one problem command:

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

If there is nothing to pull, this pipeline usually produces no output. But, if ssh-add needs to prompt for a passphrase, there is output. It just doesn't get noticed by 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

There was output to my terminal. I assume the problem is it didn't come from stdout of the git pull pipeline, which is all run_and_inspect knows about. Where did it come from? How do I fix this? I've tried redirecting stderr too (i.e. git pull 2>&1), but with no luck. Is there some way to monitor /dev/tty directly?

tl;dr (I think!)

I think this question boils down to: why isn't the passphrase prompt in 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.
mike
  • 4,901
  • 2
  • 19
  • 19

1 Answers1

0

why isn't the passphrase prompt in log.txt?

The prompt is printed from openssh load_identify_file with readpass.c read_passphrase(). The function does open(_PATH_TTY with _PATH_TTY "/dev/tty" and then write()s to it.

The output is displayed directly to the terminal /dev/tty, not with standard streams.

Just like you do with your tee /dev/tty, which means that your function will also not work. Prefer to preserve the stdout-ness of the child program and preserve buffering:

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

Is there some way to monitor /dev/tty directly?

Write your own terminal emulator and spawn your process in it, and then parse all input inside that terminal emulator. Programs like screen or tmux may be of use.

The workaround could be to download proot and open a file descriptor to some file, then create a chroot with just /dev/tty file symlinked to /proc/self/fd/<that file descriptor> and run the process with proot inside that chroot. The idea is that process will see that chroot with /dev/tty file replaced and will write to your file descriptor instead to the terminal.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111