cbashshellzshbash4

Bash reopen tty on simple program


#include <stdio.h>
#include <stdlib.h>

int main()
{
    char buf[512];
    fgets(buf, 512, stdin);
    system("/bin/sh");
}

Compile with cc main.c

I would like a one-line command that makes this program run ls without it waiting for user input.

# This does not work - it prints nothing
(echo ; echo ls) | ./a.out

# This does work if you type ls manually
(echo ; cat) | ./a.out

I'm wondering:

My question is shell and OS-agnostic but I would like it to work at least on bash 4.

Edit:

While testing out the answers, I found out that this works.

(python -c "print ''" ; echo ls) | ./a.out

Using strace:

$ (python -c "print ''" ; echo ls) | strace ./a.out
...
read(0, "\n", 4096)
...

This also works:

(echo ; sleep 0.1; echo ls) | ./a.out

It seems like the buffering is ignored. Is this due to the race condition?


Solution

  • strace shows what's going on:

    $ ( echo; echo ls; ) | strace ./foo
    [...]
    read(0, "\nls\n", 4096)                 = 4
    [...]
    clone(child_stack=NULL, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7ffdefc88b9c) = 9680
    

    In other words, your program reads a whole 4096 byte buffer that includes both lines before it runs your shell. It's fine interactively, because by the time the read happens, there's only one line in the pipe buffer, so over-reading is not possible.

    You instead need to stop reading after the first \n, and the only way to do that is to read byte by byte until you hit it. I don't know libc well enough to know if this kind of functionality is supported, but here it is with read directly:

    #include <unistd.h>
    #include <stdlib.h>
    
    int main()
    {
        char buf[1];
        while((read(0, buf, 1)) == 1 && buf[0] != '\n');
        system("/bin/sh");
    }