4

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.

1
  • Instead of killing your shell, you can just kill mc.
    – user26112
    Commented Jul 4, 2013 at 13:58

1 Answer 1

1

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 by mc.

When you run mc normally, it doesn't react when Ctrl+c is pressed, since it ignores SIGINT. The way to end mc is by pressing F10 and Enter.

When you run echo $(mc) the input goes to the mc process, so it is no wonder that when you press Ctrl+c nothing will happen, because mc ignores SIGINT. But when you run echo $(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. But echo can only return when mc 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 combination mc goes to sleep like it would if you had run mc normally and echo is still waiting for the sleeping mc.

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

echo (mc)
jobs

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .