tags:

views:

355

answers:

5
+1  Q: 

grep -r in python

i'd like to implement the unix command 'grep -r' in a python function. i know about commands.getstatusoutput(), but for now i don't want to use that. i came up with this:

def grep_r (str, dir):
    files = [ o[0]+"/"+f for o in os.walk(dir) for f in o[2] if os.path.isfile(o[0]+"/"+f) ]
    return [ l for f in files for l in open(f) if str in l ]

but that of course doesn't use a regex, it just checks if 'str' is a substring of 'l'. so i tried the following:

def grep_r (pattern, dir):
    r = re.compile(pattern)
    files = [ o[0]+"/"+f for o in os.walk(dir) for f in o[2] if os.path.isfile(o[0]+"/"+f) ]
    return [ l for f in files for l in open(f) if r.match(l) ]

but that doesn't work, it doesn't give me any matches even where the former function did. what changed? i could just split it up into a bunch of nested loops, but i'm more interested in being succinct than readable.

+4  A: 

re.match only checks the beginning of the string.

Use re.search()

From the docs:

Python offers two different primitive operations based on regular expressions: match checks for a match only at the beginning of the string, while search checks for a match anywhere in the string (this is what Perl does by default).

Mark Peters
+5  A: 

You might want to search() instead of match() to catch matches in the middle of lines, as noted in http://docs.python.org/library/re.html#matching-vs-searching

Also, the structure and intent of your code is quite hidden. I've pythonized it.

def grep_r (pattern, dir):
    r = re.compile(pattern)
    for parent, dnames, fnames in os.walk(dir):
        for fname in fnames:
            filename = os.path.join(parent, fname)
            if os.path.isfile(filename):
                with open(filename) as f:
                    for line in f:
                        if r.search(line):
                            yield line
Joe Koberg
yeah, mine is scarcely readable, ever since i read <a href="http://norvig.com/spell-correct.html">this article by Peter Norvig</a> i keep putting those 'i for i in some_generator' statements in my code...
aaronstacy
Oh its natural to want to use the powerful abstractions! I used to make monstrous multi-line constructions of map() and reduce() before list comprehensions came around - I really liked the idea of "do this, to all THESE" instead of "Ok get the next one and do... Ok get the next one and do..." But I learned my coworkers could not untangle it, and it's exactly the same to the computer.
Joe Koberg
If you like that spelling corrector you should learn haskell. The function mappings on sets is a natural http://github.com/timrobinson/spell-correct/blob/master/Correct.hs
Joe Koberg
thanks for the link, i've been looking at haskell, but i've not had the time to really get in to it yet!
aaronstacy
+1  A: 
import os, re

def grep_r(regex, dir):
    for root, dirs, files in os.walk(dir):
        for f in files:
            for m in grep(regex, os.path.join(root, f)):
                yield m

def grep(regex, filename):
    for i, line in enumerate(open(filename)):
        if re.match(regex, line): # or re.search depending on your default
           yield "%s:%d: %s" % (os.path.basename(filename), i+1, line)
J.F. Sebastian
A: 

why do you need to use regex?

path=os.path.join("/dir1","dir2","dir3")
pattern="test"
for r,d,f in os.walk(path):
    for files in f:
        for n,line in enumerate(open( os.path.join(r,files) ) ):
            if pattern in line:
                print "%s found in line: %d of file: %s" %(pattern, n+1, files)
+1  A: 

Put all this code into a file called pygrep and chmod +x pygrep:

#!/usr/bin/python

import os
import re
import sys

def file_match(fname, pat):
    try:
        f = open(fname, "rt")
    except IOError:
        return
    for i, line in enumerate(f):
        if pat.search(line):
            print "%s: %i: %s" % (fname, i+1, line)
    f.close()


def grep(dir_name, s_pat):
    pat = re.compile(s_pat)
    for dirpath, dirnames, filenames in os.walk(dir_name):
        for fname in filenames:
            fullname = os.path.join(dirpath, fname)
            file_match(fullname, pat)

if len(sys.argv) != 3:
    u = "Usage: pygrep <dir_name> <pattern>\n"
    sys.stderr.write(u)
    sys.exit(1)

grep(sys.argv[1], sys.argv[2])
steveha
+1 I was quickly able to customize this to use a more robust larger set of options.
TK