To run an interactive shell connected to a pipe, you just have to make it interactive.
input | /bin/sh -i
The shell doesn't need a terminal to be interactive - it needs interactive input. In the main, the shell's behavior when run interactively has very little to do with terminals at all - (at least according to spec) - and generally differs from a non-interactive shell's behavior where error handling is concerned more than anything else. Interactive shells tend not to exit on error conditions that would otherwise cause a non-interactive shell to quit. A shell should default to interactive-mode if input comes from a terminal, though.
Some shells may seem to require terminal input for interactive use, but this is an illusion. In fact, these shells typically work very like your example case under the hood -
bash, for example, sets up
readline to handle the terminal i/o nitty-gritty on its behalf,
zsh invokes its ZLE line-editor, and
dash (if not compiled with the build-time option SMALL) links in BSD's
libedit library. These line-editors read and process the terminal input into something like a line-by-line shell-script, which the shell then executes as appropriate.
You're not having an issue with any of those editors, though. Judging by your prompt and your execve call, you're calling up a
dash that is compiled with the SMALL build-time option (the Debian default) in which case it will just work - but the only line-editing functions you might get out of it will be those natively provided by the terminal's line-discipline (see
Your problem is that the shell doesn't have any input - when it detects EOF it dies as has already been noted elsewhere. You might do...
input | /bin/sh -i -o ignoreeof
But you will probably not like the results.
dash won't do the quit on 10th consecutive null-read thing that some shells do - it will just print...
Type 'exit' to exit the shell
...to stderr forever. A little better could be...
cat input - | /bin/sh -i
...to concactenate the shell commands in the
input file with
- stdin to its stdout and then to execute the results in an interactive
/bin/sh. This will work - though you may want to ensure you configure the
stty line-discipline to something like a canonical-state and with an appropriate
erase key so you can at least get a functioning backspace. This is probably already set correctly - but it's worth checking anyway.
echo ": some command; exec <$(tty)" | /bin/sh -i
The above methods will work because your pipeline is the current foreground job on the controlling terminal - your pipeline currently owns the terminal input and
cat is actually reading it. This is contrasted by
python -c and
echo which do not read it - they only pass-on output as generated by command-line arguments - and so when their output ends so also does your shell's input. This is true unless the shell is instructed to look for input elsewhere as I do with
echo in the second example.
The terminal is just one possible source of input of many to a shell - and it can be handled in many different ways. Your terminal's session leader - it looks like
bash - waits for your pipeline's terminal control to end so it can regain control and do the next thing. This information will be passed to it as an asynchronous signal - this is what terminals are for. Terminals multiplex a display/input pair across any number of reading/printing processes. They do this very well.
$ PS1='bgsh1: ' sh -i +m & PS1='bgsh2: ' sh -i +m &
$ bgsh1: bgsh2:
 + Stopped (tty input) sh -i +m
 - Stopped (tty input) sh -i +m
$ i=0; while [ "$((i+=1))" -lt 5 ]; do fg "%$(((i%2)+1))"; done
sh -i +m
bgsh2: kill -TSTP $$
sh -i +m
bgsh1: kill -TSTP $$
sh -i +m
bgsh2: echo $var; kill -TSTP $$
sh -i +m
bgsh1: echo $var; kill -TSTP $$
 + Stopped sh -i +m
 - Stopped sh -i +m