How can I perform a ping or traceroute in python, accessing the output as it is produced?

StackOverflow https://stackoverflow.com/questions/1151897

  •  18-09-2019
  •  | 
  •  

Question

Earlier, I asked this question:

How can I perform a ping or traceroute using native python?

However because python is not running as root it doens't have the ability to open the raw ICMP sockets needed to perform the ping/traceroute in native python.

This brings me back to using the system's ping/traceroute shell commands. This question has a couple examples using the subprocess module which seem to work well:

Ping a site in Python?

I still have one more requirement though: I need to be able to access the output as it is produced (eg. for a long running traceroute.)

The examples above all run the shell command and then only give you access to the complete output once the command has completed. Is there a way to access the command output as it is produced?

Edit: Based on Alex Martelli's answer, here's what worked:

import pexpect

child = pexpect.spawn('ping -c 5 www.google.com')

while 1:
        line = child.readline()
        if not line: break
        print line,
Was it helpful?

Solution

pexpect is what I'd reach for, "by default", for any requirement such as yours -- there are other similar modules, but pexpect is almost invariably the richest, most stable, and most mature one. The one case where I'd bother looking for alternatives would be if I had to run correctly under Windows too (where ping and traceroute may have their own problems anyway) -- let us know if that's the case for you, and we'll see what can be arranged!-)

OTHER TIPS

You should read the documentation for the subprocess module, it describes how to run an external process and access its output in real time.

Basically, you do

from subprocess import Popen, PIPE
p = Popen(['tracert', host], stdout=PIPE)
while True:
    line = p.stdout.readline()
    if not line:
        break
    # Do stuff with line

Actually, the answers in the SO question you linked to are very close to what you need. Corey Goldberg's answer uses a pipe and readline, but since it runs ping with -n 1 it doesn't last long enough to make a difference.

You can create a tty pair for the subprocess and run inside of that. According to the C standard (C99 7.19.3) the only time stdout is line buffered (as opposed to fully buffered which is what you say you don't want) is when it's a terminal. (or the child called setvbuf() obviously).

Check out os.openpty().

Untested code:

master, slave = os.openpty()
pid = os.fork()
if pid == 0:
    os.close(master)
    os.dup2(slave, 0)
    os.dup2(slave, 1)
    os.dup2(slave, 2)
    os.execv("/usr/sbin/traceroute", ("traceroute","4.2.2.1"))
    # FIXME: log error somewhere
    os.exit(1)
os.close(slave)
while True:
    d = os.read(master)
    if len(d) == 0:
        break
    print d
os.waitpid(pid, 0)

Note that having the child process (just after fork()) call setvbuf() will not work, since setvbuf() is a libc function and not a syscall. It just changes the state of the current process output, which will be overwritten on the exec call when the new binary in loaded.

Here is another approach:

# const_output.py
import sys
from subprocess import Popen

if len(sys.argv) < 2:
    print 'Usage: const_output.py "command to watch"'
    sys.exit(1)

cmd_line = sys.argv[1:]

p = Popen(cmd_line)
p.communicate()[0]

Example usage:

traceroute:

> python const_output.py traceroute 10.0.0.38
traceroute to 10.0.0.38 (10.0.0.38), 30 hops max, 60 byte packets
 1  10.0.0.38 (10.0.0.38)  0.106 ms  0.023 ms  0.021 ms

ping:

> python const_output.py ping 10.0.0.38
PING 10.0.0.38 (10.0.0.38) 56(84) bytes of data.
64 bytes from 10.0.0.38: icmp_seq=1 ttl=64 time=0.046 ms
64 bytes from 10.0.0.38: icmp_seq=2 ttl=64 time=0.075 ms
64 bytes from 10.0.0.38: icmp_seq=3 ttl=64 time=0.076 ms
64 bytes from 10.0.0.38: icmp_seq=4 ttl=64 time=0.073 ms

top:

> python const_output.py top
   # you will see the top output
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top