Welcome, guest | Sign In | My Account | Store | Cart
4

Simple script for generating all the string described by a pattern passed on the command-line:

Python, 393 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
import sys
import string

"""
This module provides general purpose routines for generating
lists of strings from patterns. Thus:

    python Pattern.py 172.16.[72-74,77-82].[101-200]

produces the following sequence of 800 IPs:

    172.16.72.101
    172.16.72.102
    ...
    172.16.82.199
    172.16.82.200

Ranges can be decimal, hexidecimal and alphabetic, e.g.

    % python Pattern.py [0-10]
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    %

    % python Pattern.py foobar_[a-z]
    foobar_a
    foobar_b
    foobar_c
    ...
    foobar_x
    foobar_y
    foobar_z
    % 

    % python Pattern.py [a-z][A-Z]
    aA
    aB
    aC
    ...
    zX
    zY
    zZ
    % 

    % python Pattern.py [0x0-0xf]
    0
    1
    2
    ...
    d
    e
    f
    % 

For hexadecimal output the leading 0x is left out because it's trivial to add:

    % python Pattern.py 0x[0xa-0xf]
    0xa
    0xb
    0xc
    0xd
    0xe
    0xf
    % 

To help formatting, zero padding on the start of the range
is added to all members of the range, e.g.

    % python Pattern.py an-sm[1-8]-g[001-100]
    an-sm1-g001
    an-sm1-g002
    an-sm1-g003
    ...
    an-sm8-g098
    an-sm8-g099
    an-sm8-g100
    % 

In addition, Pattern extends IPPatterns notation by adding
the concept of a zip using juxtaposition. Thus the sequences
produced by successive arguments are computing in step and
are displayed together. Thus to produce a simple table of
the hex ascii codes for lowercase letters is simply:

    % python Pattern.py [A-Z] 0x[0x41-0x5a]
    A 0x41
    B 0x42
    C 0x43
    ...
    X 0x58
    Y 0x59
    Z 0x5a
    % 

Similarly to produce a deck of 52 cards is simply:

    % python Pattern.py [2-9,T,J,Q,K,A][C,D,H,S]
    2C
    2D
    2H
    2S
    3C
    3D
    3H
    3S
    ...
    KC
    KD
    KH
    KS
    AC
    AD
    AH
    AS
    %
"""

def aFill( str, n ):
    """leftpad str with 'a' so it is at least n chars long"""
    return ('a'*(n-len(str))) + str

def zFill( str, n ):
    """leftpad str with '0' so it is at least n chars long"""
    return str.zfill( n )

def computeIntRange( start, finish, toInt=int, fromInt=str, fill=zFill, padLen=len ):
    """Computes a range list from a start value, finish value and optional
       int-to-string, string-to-int, pad functions and pad length."""
    n = padLen( start )
    return [ fill( fromInt(i), n ) for i in range(toInt(start),toInt(finish)+1) ]

def fromHex( h ):
    """Convert a hex string into a int"""
    return int(h,16)

def   toHex( i ):
    """Convert an int into a hex string (without the leading 0x)"""
    return hex( i )[2:]

def isHexadecimalRange( start, finish ):
    """Tests for hexadecimal range"""
    return start.startswith( '0x' ) and finish.startswith( '0x' )

def isNumericRange( start, finish ):
    """Tests for decimal range"""
    return allNumeric( start ) and allNumeric( finish )

def allIn( as, members ):
    "Tests that all elements of as are in members"""
    for a in as:
        if a not in members:
            return False
    return True

def allLower( as ):
    """Tests that all strings in as are lowercase"""
    return allIn( as, string.lowercase )

def allUpper( as ):
    """Tests that all strings in as are uppercase"""
    return allIn( as, string.uppercase )

def allNumeric( as ):
    return allIn( as, string.digits )

def sameLength( as, bs ):
    """Tests that as and bs are the same length"""
    return len( as ) == len( bs )

def lettersToInt( str ):
    """turn a string of letters into a base 26 number"""
    return reduce( lambda x, y: 26*x + y, map( string.lowercase.index, str ))

def intToLetters( i, str='' ):
    """convert a number into a string of lowercase letters"""
    if i == 0:
        return str
    else:
        return intToLetters( i/26, string.lowercase[i%26] + str )

def isUpperLetterRange( start, finish ):
    """Tests start and finish are both uppercase letter ranges"""
    return allUpper( start ) and allUpper( finish )

def isLowerLetterRange( start, finish ):
    """Tests start and finish are both lowercase letter ranges"""
    return allLower( start ) and allLower( finish )

def computeRange( start, finish ):
    if isHexadecimalRange( start, finish ):
        return computeIntRange( start, finish, fromHex, toHex, zFill, lambda x: len( x )-2 )
    if isLowerLetterRange( start, finish ):
        return computeIntRange( start, finish, lettersToInt, intToLetters, aFill )
    if isUpperLetterRange( start, finish ):
        return [s.upper() for s in computeRange(start.lower(),finish.lower())]
    if isNumericRange( start, finish ):
        return computeIntRange( start, finish )
    else:
        raise SyntaxError, "invalid range syntax"

def splitAt( s, i, gap=0 ):
    """split s into two strings at index i with an optional gap"""
    return s[:i], s[i+gap:]

def find( s, target ):
    """version of find that returns len( s ) when target is not found"""
    result = s.find( target )
    if result == -1: result = len( s )
    return result

class BadOpException( Exception ):
    pass

def doOp( op, a, b ):
    if op == '++':
        return setUnion( a, b )
    elif op == '--':
        return setDifference( a, b )
    elif op == '^^':
        return setIntersection( a, b )
    else:
        raise BadOpException

"""Implementation of Sets based on lists
   We don't use the python built-in sets because
   a) they were added in a later version (2.3?)
   b) we wanted an implemetation that presevered
   the ordering of the leftmost argument to any set
   operation even if it's slower.
"""

def setEmpty():
    """return the empty set"""
    return []

def setCopy( set ):
    """(shallow) copy a set"""
    return set[:]

def member( x, set ):
    """test for set membership"""
    try:
        set.index( x )
        return True
    except ValueError:
        return False

def setToList( set ):
    """takes a set and returns a list"""
    return set

def setAdd( set, m ):
    if not member( m, set ):
        set.append( m )
    return set

def setFromList( list ):
    """takes a list and returns a set by ignoring duplicates"""
    set = setEmpty()
    for a in list:
        setAdd( set, a )
    return set

def setSubtract( set, m ):
    """in place set removal"""
    if member( m, set ): set.remove( m )

def setUnion( as, bs ):
    """returns a new set that is the union of as and bs"""
    set = setCopy( as )
    for b in bs:
        setAdd( set, b )
    return set

def setDifference( as, bs ):
    """returns a new set that is the difference of as and bs"""
    set = setEmpty()
    for a in as:
        if not member( a, bs ):
            set.append( a )
    return set

def setIntersection( as, bs ):
    """returns a new set that is the intersection of as and bs"""
    set = setEmpty()
    for a in as:
        if member( a, bs ):
            set.append( a )
    return set

def find( s, target, i=0 ):
    """Version fo find which returns len( s ) if target is not found"""
    result = s.find( target, i )
    if result == -1: result = len( s )
    return result

def multifind( s, targets, i=0 ):
    """Find the earliest index in s which matches one of targets starting at i"""
    return min( [find( s, target, i ) for target in targets] )

def fileExpr( expr, fileStr='@' ):
    """If expression contains @file@ read in contents of file and return as a list"""
    if expr.startswith( fileStr ) and expr.endswith( fileStr ):
        return [ line.rstrip() for line in file( expr[1:-1] ).readlines() ]
    else:
        return [ expr ]

def computeList( expr, openStr='[', closeStr=']', rangeStr='-', sepStr=',', fileStr='@' ):
    """Parse and compute range in expr"""
    if expr[0] == openStr:
        result = []
        while expr[0] != closeStr:
            expr = expr[1:]
            i = multifind( expr, [closeStr,rangeStr,sepStr] )
            if expr[i] == sepStr:
                item, expr = splitAt( expr, i )
                result = result + fileExpr( item )
            elif expr[i] == rangeStr:
                start, expr  = splitAt( expr, i, 1 )
                finish, expr = splitAt( expr, multifind( expr, [closeStr,sepStr] ) )
                result = result + computeRange( start, finish )
            else:
                if i > 0: result = result + fileExpr( expr[:-1] )
                break
        return result
    elif expr[0] == fileStr:
        return fileExpr( expr[1:] )
    else:
        return [expr]

def splitOnBrackets( expr, openStr='[', closeStr=']' ):
    """Splits expr in a sequence of alternating non-bracketed and bracketed expressions"""
    n = len( expr )
    components = []
    while len( expr ) > 0:
        n = len( expr )
        target = openStr
        if expr[0] == target: target = closeStr
        i = multifind( expr, [target] )
        if target == closeStr: i += 1
        components.append( expr[:i] )
        expr = expr[i:]
    return components

def product( fields, i=0, result='' ):
    """Takes a list of list of fields and produces a generator
       that permutes through every possible combination of fields in order."""
    if i == len( fields ):
        yield result
    else:
        for field in fields[i]:
            for x in product( fields, i+1, result + field ):
                yield x

def pattern( p ):
    """Splits pattern p into it's constituents and produces
       a iterator than generates all possible permutation"""
    return product( map( computeList, splitOnBrackets( p ) ) )

def setExpression( expr ):
    """Handles IP expressions of the form:
          <exp> = <exp> [-- <exp> | ++ <exp> | ^^ <exp>]*"""
    subexp, expr = splitAt( expr, multifind( expr, ['--', '++', '^^'] ) )
    accum = setFromList( pattern( subexp ) )
    while expr != '':
        op, expr = splitAt( expr, 2 )
        subexp, expr = splitAt( expr, multifind( expr, ['--', '++', '^^'] ) )
        accum = doOp( op, accum, setFromList( pattern( subexp ) ) )
    return iter( setToList( accum ) )

def expression( ps, joinStr=' ' ):
    return zipGenerators( map( setExpression,  ps ), joinStr )

def zipGenerators( ps, joinStr ):
    """Takes a list of string iterators and produces an iterator of strings joined by joinStr"""
    while True:
        yield joinStr.join( [p.next() for p in ps] )

def Pattern( ps ):
    return expression( ps )

if __name__ == '__main__':
    for result in expression( sys.argv[1:] ):
        print "%s" % result

Sometimes you need to generate a list of IP addresses or hostnames that follow a systematic pattern for some bash script or the like. This handy dandy script let's you do that...

Add a comment

Sign in to comment

Created by Guy Argo on Fri, 3 Dec 2004 (PSF)
Python recipes (4587)
Guy Argo's recipes (3)

Required Modules

  • (none specified)

Other Information and Tasks