ZSH: Output from stdout from within while read gets lost

Viewed 326

I ran into a weird ZSH issue. I have trimmed the script to the smallest form that still reproduces the issue.

What I'm simulating here, is monitoring a directory for changes via a subprocess (the actual script uses fswatch). Because I'm monitoring, it means I cannot run a command beforehand and save the output. A (working) example is the following:

(echo "text.txt"; echo "text.txt") | while read filepath; do scp "$filepath" "trip:~/tom/"; echo $?; done
text.txt                    100%  822    51.1KB/s   00:00
0
text.txt                    100%  822    99.1KB/s   00:00
0

Now, when I add a delay in the middle, seemingly any delay that is longer than the runtime of the scp command inside the while-loop, suddenly the second output and any subsequent command of scp will not visible:

(echo "text.txt"; sleep 2; echo "text.txt") | while read filepath; do scp "$filepath" "trip:~/tom/"; echo $?; done
text.txt                    100%  822    51.1KB/s   00:00
0
0

Interestingly enough, the echo still works and errors show up like you may expect.

(echo "doesnotexist.txt"; sleep 2; echo "doesnotexist.txt") | while read filepath; do scp "$filepath" "trip:~/tom/"; echo $?; done
doesnotexist.txt: No such file or directory
1
doesnotexist.txt: No such file or directory
1

What is going on here? I hope someone can help me clear this up. Thank you!

Edit:

After some more fiddling, it appears this has something to do with ZSH, as it works in a normal bash-shell.

1 Answers

Running the whole while loop in a subshell does not have the problem. This seems work:

(echo "text.txt"; sleep 2; echo "text.txt") | (while read filepath; do scp "$filepath" "trip:~/tom/"; echo $?; done)

Some digging in the scp source revealed that the non-output is caused by this check returning false: progressmeter.c#L81-L85

static int
can_output(void)
{
    return (getpgrp() == tcgetpgrp(STDOUT_FILENO));
}

(I.e. the process group id of the scp process suddenly being different from the "foreground process group on the terminal associated to stdout").

One difference between bash and zsh is that zsh runs while loops in the main shell process, while bash does not (which I gleaned from here). But this is as far as I am able to dig right now.

Related