System info:
macOS Sierra 10.12.6
zsh 5.4.2 (x86_64-apple-darwin16.7.0)
GNU bash, version 4.4.12(1)-release (x86_64-apple-darwin16.3.0)
Scroll to the EXAMPLES at the bottom if you just want to dig in to the simplified examples that I made.
NOTE: I am not a big zsh user.
I was looking at the fzf keybindings for bash and zsh.
Notice how they both run a variable command $(__fzfcmd). __fzfcmd by default outputs fzf to stdout and the parameter substitution just runs command (fzf) resulting from the output.
One difference between the bash and zsh script is that the bash one further pipes the output of $(__fzfcmd) but zsh just captures it inside an array. My guess is because of a problem in zsh when you further pipe the output of fzf where you can't input to fzf and the process piped to by fzf doesn't get any stdin. Your only choice is to ^Z or ^C. ^C seems to background the process for some reason. Or maybe they just wanted it in an array so they could could run zle vi-fetch-history on it. The bash version does some magic in the key binding with "\e^": history-expand-line
Now fzf isn't important. It seems like you just need a program that outputs to the tty to be called by parameter substitution to cause this problem. So I will show some simpler examples.
Here are some other commands that output to the tty that can cause this problem in zsh:
- vipe (run editor in middle of a pipe)
'vim -'(make vim read from stdin. similar to vipe but won't output to stdout)
In the examples below, replace every occurrence of vipe with vim - if you don't want to do a separate install. Just remember that vim - won't output the editor contents to stdout like vipe does.
EXAMPLES:
1) echo 1 | vipe | cat # works in both bash and zsh
2) echo 1 | $(echo vipe) | cat # works in bash only. zsh problem with no output until I hit `^C`:
^C
zsh: done echo 1 |
zsh: suspended (tty output) $(echo vipe) |
zsh: interrupt cat
# seems like the process is backgrounded. I can still see it in jobs command
3) cat <(echo 1 | $(echo vipe)) # zsh and bash has the problem. I'm guessing because
# the file isn't finished writing and cat is
# blocking vipe's tty output
# both their `^C` output is just:
^C # nothing special, as expected
4) cat < <(echo 1 | $(echo vipe)) # works in both bash and zsh
5) echo 1 | $(echo vipe) > >(cat) # works in both bash and zsh
# The following don't have and input pipe to vipe.
# Type something then send EOF with ^D
6) vipe | cat # works for both
7) $(echo vipe) | cat # works for both
Now, I'm mostly wondering why 2) has a problem for zsh but not for bash and why 4) and 5) fixes the problem for zsh.
The requirements for zsh to have this problem to seem to be exactly what I put in the title:
- input pipe
- command run by variable/parameter substitution that has
ttyoutput - output pipe
UPDATE
I added another workaround that doesn't cause zsh to have this problem, 5). It's similar to 4) but instead of redirecting stdout directly into stin, I redirect it into a file that redirects into stdin using process substitution.
pswill tell you, in none of these cases are the shells frozen or stuck. They are simply waiting for child processes in the normal way; and they will indeed loop back to prompting for input in the normal way once those child processes are suspended or terminated. Your question title and body include an implicit false premise. "Why does my shell freeze?" is an unanswerable loaded question when your shell is not actually freezing in the first place. You would have a better question for removing this implicit false premise.(echo | $(echo vipe) | cat)