Suppose you want to have a program that ingest a string and then reattach stdin when done.

I know that it is possible to do so by doing this trick:

(echo id; cat) | sh

Which works well. It doesn't close stdin since it is used by cat. But the output is messy.

Digging deeper I understood that it is lacking of a tty.

If my comprehension is right, sh being in a pipe didn't open a tty.

The trick I've found is to use expect.

Simply put this in a file:

spawn sh

Then when cat gives you back the control, do:

expect exp.sh

This is nice because it gives me a tty to do everything I need: ssh, tmux and so on...

The only thing I don't understand why is that I see everything I typed, back to my terminal.


(echo whoami; cat) | sh
expect exp.sh
spawn sh
sh-3.2$ whoami

Note the whoami output back before my login on the last line.

Could somebody explain to me why? It is obviously not a property of stdout from cat because it is attached to sh directly -- yes, even if it was dumb I tried the following: (echo id; cat > /dev/null) | sh then nothing happened.

So is it a property of a tty/pty to not display back what has been typed from the keyboard?

{ echo 'exec <&3 3<&-; id' | sh -si; } 3<&0

Seems to work with most sh implementations. We ask for an interactive shell with -i (otherwise, some shells would disable it on the ground that stdin is not a terminal device), start with the shell's stdin being the pipe, but then with exec <&3 instruct it to change it to the original (outer) stdin which we've saved earlier on the file descriptor 3 with 3<&0.

We do that before running id but still on the same command line, so that stdin is restored for id as well. It doesn't matter for id as it doesn't read its stdin, but it would be interactive commands like editors or shells.

As to why you see what you type in (echo id | cat) | sh. It looks like your implementation of sh which seems to be a very old version of bash is running in interactive mode. You can see that with the fact that it is issuing a prompt as if -i was used.

Then it does implement its own line editor, but fails to disable the tty device own line editor (because its stdin is the pipe and not the tty device), so you get the echo of what you type by the tty device line discipline, and once you press enter, cat sees all the content of that line buffer including a trailing newline at once from the pipe which it writes straight away to bash, and bash outputs the echo of that as part of its own line editor.

I suspect that if you did:

stty -icanon -echo; (echo id; cat) | sh; stty sane

You'd get a better behaviour, and may be able to use readline line editing functionalities as bash would receive every character you type directly as they come.

For other options to have a command been run automatically before the user takes over an interactive shell, see:

| improve this answer | |
  • Thank you Steph! it was really informative and I'm learning a lot. Your first example works perfectly, the last one works too but as you might have suspected, I need to do the expect trick, once it is done, it works very well. I am slowly going to digest everything and really understand what your wrote. I might be back with some questions too! – tehmoon Mar 21 '18 at 14:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.