4

I want to do something similar to the second answer here (but not quite similar): Simulate Ctrl-C keyboard interrupt in Python while working in Linux

It's much simpler and I think I'm just missing something. Say, from a python script, I just want to call 'ping' and terminate it after the 10th time. I'm trying to do it like from the link above:

p = subprocess.Popen(['ping', 'google.com'], stdout=subprocess.PIPE)
for line in p.stdout:
  print line
  if re.search('10', line):
    break
os.kill(p.pid, signal.SIGINT)

But it doesn't work.

And I also want the regular output of 'ping' to be displayed. How do I do this?

EDIT: It's not actually a 'ping' that I want to do. I'm just using it as an example of a command with continuous output, which in time, I would like to terminate.

More specifically, I'm running an old version of BitTorrent (v5.0.9 from the 3rd answer here: Where to find BitTorrent source code?) and I'm calling it via python script. The bittorrent-console.py is a simple terminal version, hence 'console'. It outputs multiple lines periodically. Something like:

saving:       filename
file size:    blah
percent done: 100.0
blah:         blahblah

I'm actually calling it by:

subprocess.call(['./bittorrent-console.py', 'something.torrent'])

I want to terminate it automatically when I see it's 100.0 in 'percent done:'.

EDIT: I'm running on CentOS, Python 2.6.

Community
  • 1
  • 1
joalT
  • 85
  • 1
  • 1
  • 7
  • 1
    note also that `10` might be part of the IP address – Ron Klein Sep 27 '13 at 20:19
  • 1
    Yep, I realized that, but assume there's no 10 in the address or the time. But again, just an example. It could've been `if re.search('icmp_seq=10'`. – joalT Sep 27 '13 at 20:31
  • Note that `subprocess.Popen` and `subprocess.call` differ in one *very* important detail. The former is asynchronous, the latter is syncrhonous. In the latter case, by the time you call `os.kill()`, the app is already dead and `os.kill()` has no effect whatsoever. – Robᵩ Sep 27 '13 at 21:20
  • Ah. Yes. That is indeed true. I want to keep on using `call` though, because there's no lag in output like when I tried the answer below. It simply has just called bittorrent-console and left it running. I don't know how to terminate it when the download's done except by Keyboard Interrupt. – joalT Sep 27 '13 at 21:45
  • Would you happen to know if inside the script, after `call`, there's a way to get the process id of what I just called? – joalT Sep 27 '13 at 22:02
  • BTW, you can share a specific answer just by using its `share` link. That's how we can look at the same answer while having different sort options (active/oldest/votes). – Ron Klein Sep 28 '13 at 04:44

2 Answers2

3

Firstly, you want to use p.stdout.readline. I'm not sure why, but for line in p.stdout doesn't seem to flush. Perhaps it's buffered.

Secondly, you should use sys.stdout.write(line), because print always appends something. In Python 3 you can use print(line, end=""), though.

Also, you should prefer p.kill to os.kill. I'm not sure why you're using os.kill.

import os
import signal
import subprocess
import sys

p = subprocess.Popen(['ping', 'google.com'], stdout=subprocess.PIPE)
while True:
    line = p.stdout.readline()

    sys.stdout.write(line)
    sys.stdout.flush()
    if '10' in line:
        break

p.kill()
Veedrac
  • 58,273
  • 15
  • 112
  • 169
  • I think this will do the trick. Worked for the `ping` example. I'll try it later on what I'm really working on. – joalT Sep 27 '13 at 21:29
  • This doesn't output very well. It takes time to write and flush per line since bittorrent-console prints out multiples lines at a time. Is there any way that I could just use subprocess.call, catch the '100.0' and terminate bittorrent-console? – joalT Sep 27 '13 at 21:40
  • No, there is not. You can pass `bufsize=0` to `subprocess.Popen` but there is nothing more that you can do (parsing sub-lines could help if lines are given out incrementally, but I doubt it). – Veedrac Sep 27 '13 at 21:49
  • Well, your answer did solve my question. I don't want the slow update on terminal output though. I need it to still look real-time. I guess I'll have to find another way. – joalT Sep 27 '13 at 21:57
2

This does exactly what you want and works perfectly on my OS X machine:

import subprocess
import re

def get_output(cmd, until):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    ret = []
    while True:
        line = p.stdout.readline()
        ret.append(line)
        if re.search(until, line):
            break
    p.kill()
    return ret

 print ''.join(get_output(['ping', 'google.com'], until='10'))
Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111