Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Paging output from python

Tags:

python

I'm trying to implement something similar to git log, which will only page the output if the log is of a certain length. If you're not familiar with git, I'm essentially trying to achieve this:

python some_script.py | less

With some help from the paging implementation in python2.6/pydoc.py, I was able to come up with this:

import os
text = '...some text...'
pipe = os.popen('less', 'w')
pipe.write(text)
pipe.close()

which works great, but os.popen() is deprecated. I've considered writing to a temp file and calling less with its path, but that doesn't seem ideal. Is this possible with subprocess? Any other ideas?

EDIT:

So I've gotten subprocess working. I was able to give it the text variable with Popen.communicate(text), but since I really want to redirect print statements, I've settled on this:

  import os, sys, subprocess, tempfile

  page = True
  if page:
      path = tempfile.mkstemp()[1]
      tmp_file = open(path, 'a')
      sys.stdout = tmp_file
  print '...some text...'
  if page:
      tmp_file.flush()
      tmp_file.close()
      p = subprocess.Popen(['less', path], stdin=subprocess.PIPE)
      p.communicate()
      sys.stdout = sys.__stdout__     

Of course, I'd end up wrapping it into functions. Does anyone see a problem with that?

like image 468
nren Avatar asked Jul 18 '11 04:07

nren


4 Answers

How about this:

import pydoc
text = '... some text ... '
pydoc.pager(text)

This (on my opensuse linux box) sends the text to a pager ('less' in my case), and works the same as calling "help(... python command...)" within the Python interpreter.

like image 184
ralhei Avatar answered Nov 17 '22 03:11

ralhei


It is a good idea to be explicit in your code, so that it shows that you use a special print function printc() instead of the standard one. Using subprocess.call() is also sufficient (you don't need the pipe machinery). Furthermore, you can save a variable by not storing the name of the temporary file:

from __future__ import print_function

import subprocess, tempfile

page = True  # For tests

# Definition of a printc() function that prints to the correct output
if page:
    tmp_file = open(tempfile.mkstemp()[1], 'w')  # No need to store the name in a specific variable
    def printc(*largs, **kwargs):
        if 'file' not in kwargs:  # The code can still use the usual file argument of print()
            kwargs['file'] = tmp_file  # Forces the output to go to the temp file
        print(*largs, **kwargs)
else:
    printc = print  # Regular print

# Main program:

printc('...some text...', 'some more text', sep='/')  # Python3 syntax

# Paging of the current contents of the temp file:
if page:
    tmp_file.flush()  # No need to close the file: you can keep printing to it
    subprocess.call(['less', tmp_file.name])  # Simpler than a full Popen()

This way, you get the flexibility of Python 3's print function, with a code that explicitly shows that you're doing some fancy printing stuff. This scales better with larger programs than modifying the "global" sys.stdout variable in some locations of your code.

like image 33
Eric O Lebigot Avatar answered Nov 17 '22 02:11

Eric O Lebigot


Use subprocess.Popen instead.

http://docs.python.org/library/subprocess.html#subprocess-replacements

http://docs.python.org/library/subprocess.html#subprocess.Popen

There is even a note about this in the os.popen docs.

http://docs.python.org/library/os.html#os.popen

like image 3
agf Avatar answered Nov 17 '22 02:11

agf


I didn't like executing external commands, so I wrote pager in pure Python. It still has a problem - piped input works only for Windows.

like image 2
anatoly techtonik Avatar answered Nov 17 '22 02:11

anatoly techtonik