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
tty
output - 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.
ps
will 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)