pexpect / ptyprocess

Run a subprocess in a pseudo terminal
https://ptyprocess.readthedocs.io/en/latest/
Other
222 stars 71 forks source link

Help to the xonsh shell #77

Closed anki-code closed 6 months ago

anki-code commented 6 months ago

Hello! Thank you for awesome work here!

I just want to let you know about xonsh shell project and that your awesome knowledge can help this project very high.

During my effort around understanding and fixing cases around running interactive tools I diven into PTY I found ptyprocess.

If you have time and chance to take a look into how xonsh works with terminal it will be very helpful to this pure open source crowd development project.

Thanks!

For community

⬇️ Please click the 👍 reaction instead of leaving a +1 or 👍 comment

takluyver commented 6 months ago

After a quick look, I think my answer would be that the most obvious ways to capture output from subprocesses should capture only stdout, not stderr. Capturing stderr should require some extra option, as in $(foo 2>1) (in bash). I think this is basically the point of having two separate output streams, that 'normal' output capturing separates them out.

That's an answer about shell design, though, not really anything to do with PTYs. And I've never tried to design a shell, so don't place much weight on my thoughts. :slightly_smiling_face:

anki-code commented 6 months ago

Thank you for your answer! I have no special question now so I'll just close this issue with impressing respect to you.

anki-code commented 6 months ago

@takluyver could you please take a look into the case I'm trying to crack.

There is popular fzf tool and owner told me how it works:

In xonsh I see that during running the command (Popen) there is capturing stdin/stdout/stderr and command hangs but my question not about it.

Please advice how I can send SIGINT to this Popen-executed command if it reads /dev/tty for user input? I don't understand how to do it and may be you give me the thread to follow. Because xonsh uses PopenThread I can't just press Ctrl+C to send SIGINT to fzf. I can catch pressing Ctrl+C but after this I need to send it to the fzf process but I don't understand how to send it to fzf /dev/tty.

Many thanks!

takluyver commented 6 months ago

If you've got a Popen object, the obvious way to send SIGINT is popen.send_signal(signal.SIGINT), which is a wrapper around the C kill() function or similar (I'm only thinking about Unix-y platforms).

The direct equivalent of pressing Ctrl-C on a program in the terminal would be writing the single byte b'\x03' to the 'master' side of the PTY, where the 'slave' side is connected to the process (I don't like these terms, but PTYs are still documented that way). Normally the kernel intercepts this and sends SIGINT to the foreground process in the terminal, but it's possible for the process to disable that and receive the byte as input instead. If you've created the pty (e.g. using this library), you already have a FD for the 'master' side; I don't know if it's possible to get the 'master' side from inside the terminal.

anki-code commented 6 months ago

I tried to manually do this with kill -s INT but have no success:

!(fzf)  # xonsh
pstree -s fzf
# pid=11 for xonsh, pid=22 for subprocess fzf
kill -s INT 22
# No reaction - fzf still in `pstree` without changes.

I think (hypothesis) this is because it is waiting input from /dev/tty but because it ran in thread it lost connection to current tty...

takluyver commented 6 months ago

Sending a signal directly should be independent of anything to do with the terminal. But it's common to handle (or even ignore) SIGINT. It looks like fzf is handling it here:

https://github.com/junegunn/fzf/blob/d18d92f925f791271b095df968d290ced05d065a/src/terminal.go#L2880

If this is meant to quit fzf, and you can't reproduce it with the UI visible, you could try using strace to see what it's doing when it gets the signal. My experience with strace is that you get a lot of output to make sense of, though.