2

The task is: Try to send ping in python using the most basic form like "ping 8.8.8.8". After some time terminate the ping command (In a terminal, one will do Ctrl+C) and get its output. The last several lines of output which shows the ping statistics are of particular interest.

Two methods tried, did not work. My OS version is Mac OS X 10.10.1.

First method uses module pexpect, and ping will stop after about 17 seconds though I did not ask it to stop:

import pexpect
import time
child = pexpect.spawn('ping 8.8.8.8')
(x, y) = child.getwinsize()
print x
print y
time.sleep(21)
child.terminate()
x = child.read()
print x

Second Method uses module subprocess, and the last several lines of ping output are lost:

import time
from subprocess import PIPE, Popen
child = Popen(['ping', '8.8.8.8'], stdin = PIPE, stdout = PIPE, stderr = PIPE)
time.sleep(5)
child.terminate()
x = child.stdout.read()
print x
x = child.stderr.read()
print x

I'd appreciate any help! "ping -c XXX" is not accepted.

  • related: [Stop reading process output in Python without hang?](http://stackoverflow.com/a/4418891/4279) – jfs Jan 17 '15 at 01:18
  • The reason the first method fails is because the pty buffer is way too small, and we have to keep using readline() to move the output out of the buffer. – brokenMotor Jan 19 '15 at 18:22

3 Answers3

3

ping will block in your code as soon as it fills its stdout OS pipe buffer (~65K on my system). You need to read the output:

#!/usr/bin/env python
import signal
from subprocess import Popen, PIPE
from threading import Timer

child = Popen(['ping', '8.8.8.8'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
Timer(5, child.send_signal, [signal.SIGINT]).start() # Ctrl+C in 5 seconds
out, err = child.communicate() # get output
print(out.decode())
print('*'*60)
print(err.decode())
jfs
  • 346,887
  • 152
  • 868
  • 1,518
  • Thanks for the answer. If I do want to run ping for a long time, maybe for a day, what should I do? Maybe writing the output to a file? It seems using either Popen.communicate() or Popen.stdout.read() is not the crucial point because I won't write to the PIPE. Any suggestions? – brokenMotor Jan 17 '15 at 03:21
  • @user3186925: the code should work as is for a day (replace `5` with `86400`). Note two things: 1. `Timer().start()` returns *immediately* (unlike `sleep`) 2. `.communicate()` reads *both* stdout and stderr concurrently. If you want to see the output while the subprocess is running; see [Python: read streaming input from subprocess.communicate()](http://stackoverflow.com/a/17698359/4279) – jfs Jan 17 '15 at 03:36
1

The second solution you have is great. There's just one issue with obtaining your desired behavior (getting the ping's "conclusion"): You're sending the wrong signal to the process.

When you terminate the process from a shell, you traditionally send a SIGINT signal. See "bash - How does Ctrl-C terminate a child process?". This allows the process to "wrap up" (e.g., cleaning up temprorary files, providing debug information).

import signal

# Open process

child.send_signal(signal.SIGINT)

# Provide some time for the process to complete
time.sleep(1)

# Echo output

Popen.terminate, which you're using now, sends a SIGTERM instead of a SIGINT.

Community
  • 1
  • 1
Jacob Budin
  • 9,136
  • 4
  • 29
  • 35
  • Thanks! It is pity that I do not have enough reputation to up vote. – brokenMotor Jan 17 '15 at 01:21
  • @user3186925 You're welcome. You can (and should) accept this answer using the checkmark if it answers your question. This helps future readers find it and makes the site a more valuable resource. – Jacob Budin Jan 17 '15 at 01:24
  • Upvote for the `SIGINT` suggestion but I have to downvote for *"The second solution you have is great."* -- the solution introduces a data-dependent bug -- the child process may block while producing the output. It violates a simple rule: if you use `PIPE`; you should read from the pipe (the "*indefinite* amount of time" indicates that the output may be larger than OS pipe buffer). – jfs Jan 17 '15 at 01:49
  • @J.F.Sebastian The issue was the signal. We both agree on that. If you think providing words of encouragement is worthy of a down vote because you can write a more robust program, so be it. – Jacob Budin Jan 17 '15 at 02:09
  • @JacobBudin: it is not nice to encourage hard to debug bugs. When all tests pass but the script fails in production in a day or two and you can't reproduce it manually while running manually unless you know what you should be looking for (thanks to me). That's how this issue (overflowing OS pipe buffers) looks like in real life. – jfs Jan 17 '15 at 03:41
  • 1
    @J.F.Sebastian Thanks to you? Your answer "solves" an issue that simply *doesn't exist* in the question. That said, your answer offers valuable insight, but there's *no* reason to harass me over mine. – Jacob Budin Jan 17 '15 at 15:45
1

Popen.terminate() sends SIGTERM on Posix OSs. However, by default CTRL+C sends SIGINT. So to get similar behavior like pressing CTRL+C, you can do something like this:

...
import signal
...
time.sleep(5)
child.send_signal(signal.SIGINT)
...
mkj
  • 2,463
  • 5
  • 22
  • 28