1

I am used to kick off command by subprocess.Popen in my python scripts. Most of them like:

   try:
       while True:
           line = proc.stdout.readline()
           if(proc.Poll() is not None):break;
           parse(line)
   except(KeyboardInterrupt) as E:
       kill(proc)

Now I have a case where a command(a simulator tool) receives "CTRL-C" into the debug mode(command line interface) to accept debug instructions, quit when exist instructions and resume to run. it seems that Popen doesn't support to pass the standard input to argument stdin like stdin=input. I know I can use os.system(cmd) which using the standard input/output as its input/output, but that is not my solution.

so someone can show me another solution? thanks in advance.

TRY: As Ahmed AEK said, I have a try:

proc = Popen(shlex.split(cmd), stdin=sys.stdin, stdout=sys.stdout, stderr=STDOUT)
try:
    proc.wait() #how do I get output from stdout
except KeyboardInterrupt:
    proc.send_signal(signal.SIGINT)
    proc.wait()

Now, "CTRL-C"(SIGINT) can force the command into the debug mode. its function likes os.system(cmd). But, how to capture the output from stdout(sys.stdout) to search some keywords that is reason why I don't use the os.system(...), but subprocess.Popen(...)(using proc.stdout.readline() when stdout=PIPE).

  • updated my answer to provide direct solution for you. – Phyo Arkar Lwin Feb 17 '23 at 05:10
  • thank you, Phyo Arkar Lwin All I want is that the command will receive a 'CTRL-C' and then entry into a debug mode, continue to accept the instructions from a keyboard , finally quit from the debug mode and resume to run. as you know, the command is a kind of simualtor in my case. – hyperion007 Feb 17 '23 at 05:53
  • I had included that part in the answer as well. . Had you tried handling the signal handling code ? Use the signal handler to enter into debug mode . If signal is send via Ctrl+C or any other singaling system it should enter to debug mode. – Phyo Arkar Lwin Feb 20 '23 at 08:45

2 Answers2

1

ctrl+c is a signal, a signal is not passed to the standard input (by default), it's sent to all the processes connected to the terminal and each process is responsible for handling it (so both parent and child must handle the KeyboardInterrupt Exception or register a signal handler), forwarding stdin from parent to child is not going to change that.

if you still want to forward stdin (which only forwards text written to the console), then you could pass stdin=sys.stdin instead of PIPE.

Edit: To be able to enter debug mode in child on ctrl+c, the parent must register a signal handler to skip SIGINT and the child must register another to pause on ctrl+c, and the child should have stdin=sys.stdin, stdout=sys.stdout to print and read from console.

Ahmed AEK
  • 8,584
  • 2
  • 7
  • 23
  • 3
    More precisely, Control-C is intercepted by the terminal, which sends SIGINT to each process in the current foreground process group (loosely speaking, the current foreground job and any processes spawned by that job). – chepner Feb 16 '23 at 16:13
  • thanks Ahmed AEK and chepner for your detailed explanation, now I can understand the mechanism behind this. all I want is that the command will receive a 'CTRL-C' and then entry into a debug mode, continue to accept the instructions , and finally quit from the debug mode and resume to run. – hyperion007 Feb 17 '23 at 05:44
  • @hyperion007 the edit should answer your question – Ahmed AEK Feb 17 '23 at 08:32
  • @Ahmed AEK I update my question as you said. Do you have an idea to capture output from stdout when stdout=sys.stdout. It seems that stdout can be changed dynamically. thanks again – hyperion007 Feb 18 '23 at 15:14
  • @hyperion007, you need to make the child use a pipe instead of `sys.stdout` and have the parent read from `proc.stdout`, using `sys.stdout` makes you bypass the parent, so he won't have to read `proc.stdout` himself (which is a lot more tricky than you may think) – Ahmed AEK Feb 18 '23 at 15:22
1

You would want to send interrupt signal. Use os.kill or kill -SIG $SUBPROCESS_PID to the process id of subprocess . It is same as doing Ctrl+C but sending signal directly to the subprocess.

To do this , you need to know the PID of the Subprocess that is spawned.

import signal,shlex
from subprocess import Popen
cmd = 'top'
sub_proc = Popen(shlex(cmd), stdin=PIPE, stdout=PIPE, sterr=STDOUT)
with open("./pid",'w') as f:
    f.write(sub_proc.pid)

Also you need to handle signal in commands you want to print debug info

def signal_handler(sig, frame):
    print('Received SIGINT  (Ctrl+C) ')

signal.signal(signal.SIGINT, signal_handler)
print('Awaiting Signal')
signal.pause()

After you get the PID , you can do

cat ./pid | kill -SIGINT to the process or , from python :

import os

with open("./pid",'r') as f:
   pid = int(f.read())

os.kill(pid, signal.SIGINT)

Or you can also send signal by doing CTRL+C its the same.

Phyo Arkar Lwin
  • 6,673
  • 12
  • 41
  • 55
  • thanks , fixed. haven't got a chance to run yet. – Phyo Arkar Lwin Feb 17 '23 at 05:20
  • thank for your anwser and example. No, I am not intended to kill the subprocess. Just the command submitted by subprocess will take 'CTRL-C' into a debug mode to accept debug instructions from input. – hyperion007 Feb 17 '23 at 05:41
  • check how to handle the signal at second code block . and `os.kill` or `kill -SIG` won't kill the process if the signal is handled , it will send the signal in this case `SIGINT` , and whatever you have done with the signal handler will do. – Phyo Arkar Lwin Feb 18 '23 at 09:35
  • @hyperion007 have you tried handling with `signal_handler` , and do your debug mode there? – Phyo Arkar Lwin Feb 20 '23 at 08:47
  • Yep, SIGINT can be transported into signal_handler and the command(simulator) was into debug mode. My script is a kind of tool which issue different commands and get their feedback to do the analysis. Now I'm in the dilemma, I have to use the ```stdout=PIPE``` if want to get the output of command; use ```stdout=sys.stdout``` if let one command into debug mode. – hyperion007 Feb 23 '23 at 02:36