The program mc
(Midnight Commander) is not intended to be used for command substitution, but I would like to know why different shells behave differently when this program (and it seems like other curses programs in general) are used as a command substitution target.
In the fish shell, issuing the command below will print the string "Cannot get terminal settings: Inappropriate ioctl for device (25)\n\n".
echo (mc) # fish command substitution syntax
Another interesting thing to note is that after this command returns you can issue jobs
to see that the mc
process was suspended.
The equivalent command in sh, bash, and zsh hangs the shell, and by hang I mean that I cannot kill the command or suspend it using C-z/C-c. The only way I can recover from the command is by killing my shell process and restarting it (from a different shell).
echo $(mc) # hangs in sh, bash, zsh
Why does the fish shell not hang? How does it handle curses or command substitution or etc. differently to produce this behavior? I feel like this is important because it might be a desirable property for other shells to have; no command should be able to render a shell totally inoperable by accident.
EDIT: Another way to answer this question would be to show why the following program gets suspended when used as a command substitution target in the fish shell. Like mc
, it uses curses, but it is modified to reopen stdin when it detects stdin is not a tty. This allows curses mode to be used in all shells but fish.
#define _XOPEN_SOURCE
#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
// start curses mode
SCREEN* s = NULL;
FILE* out = stdout;
if(!isatty(fileno(stdout))) {
out = fopen("/dev/tty", "w");
// Should really test `out` to make sure that worked.
setbuf(out, NULL);
}
// Here, we don't worry about the case where stdin has been
// redirected, but we could do something similar to out
// for input, opening "/dev/tty" in mode "r" for in if necessary.
s = newterm(NULL, out, stdin);
printw("test");
getch();
endwin(); // end curses mode
delscreen(s);
puts("/home/matt");
return 0;
}
See this question for more info: https://stackoverflow.com/questions/17450014/ncurses-program-not-working-correctly-when-used-for-command-substitution. This code was a solution posted to my original question on Stackoverflow. The solution works for all shells but fish.
EDIT: Tried to find more information by booting up fish in gdb. Used the following gdb command sequence and got the following output:
(gdb) break main
Breakpoint 1 at 0x41fe70: file fish.cpp, line 417.
(gdb) run -c echo (mc)
Starting program: /home/matt/builds/fish-shell/fish -c echo (mc)
Cannot get terminal settings: Inappropriate ioctl for device (25)
The debugger pauses here and does not produce any more output. This is strange because the breakpoint at main in never hit. The fish shell needs to parse its command line arguments before doing command substitution, but I am not sure how the threading affects observing this in the debugger.
Best Answer
Ok, I think I have a clue what is going on.
The behaviour you are observing in zsh and bash when executing
echo $(mc)
is caused bymc
.When you run
mc
normally, it doesn't react when Ctrl+c is pressed, since it ignoresSIGINT
. The way to endmc
is by pressing F10 and Enter.When you run
echo $(mc)
the input goes to themc
process, so it is no wonder that when you press Ctrl+c nothing will happen, becausemc
ignoresSIGINT
. But when you runecho $(mc)
and press F10 and Enter it will react. (When I then press F10 and Enter again it actually quits; it should quit at the first try but I have no idea why it does not.)From this fact I deduce that Midnight Commander is running normally, but the output from it is put into the shell buffer, so it can later be used by
echo
. Something we should consider normal.Also you think that bash and zsh are hanging, but I would say that they are not hanging but waiting for the
echo
command to return. Butecho
can only return whenmc
returns, which it only does when F10 and Enter are pressed. With this we can also explain what happens when you press Ctrl+z. By pressing the combinationmc
goes to sleep like it would if you had runmc
normally andecho
is still waiting for the sleepingmc
.So everything is normal behavior. Except for what fish does. fish seems to start commands run with
echo ($command)
in the background. It kind of makes sense, since such a command normally does not need any input. For the why and how I have no answer. But you can see that it runs in the background when you enter