1#! /usr/bin/env python 2 3# Change the #! line occurring in Python scripts. The new interpreter 4# pathname must be given with a -i option. 5# 6# Command line arguments are files or directories to be processed. 7# Directories are searched recursively for files whose name looks 8# like a python module. 9# Symbolic links are always ignored (except as explicit directory 10# arguments). Of course, the original file is kept as a back-up 11# (with a "~" attached to its name). 12# 13# Undoubtedly you can do this using find and sed or perl, but this is 14# a nice example of Python code that recurses down a directory tree 15# and uses regular expressions. Also note several subtleties like 16# preserving the file's mode and avoiding to even write a temp file 17# when no changes are needed for a file. 18# 19# NB: by changing only the function fixfile() you can turn this 20# into a program for a different change to Python programs... 21 22import sys 23import re 24import os 25from stat import * 26import getopt 27 28err = sys.stderr.write 29dbg = err 30rep = sys.stdout.write 31 32new_interpreter = None 33 34def main(): 35 global new_interpreter 36 usage = ('usage: %s -i /interpreter file-or-directory ...\n' % 37 sys.argv[0]) 38 try: 39 opts, args = getopt.getopt(sys.argv[1:], 'i:') 40 except getopt.error, msg: 41 err(msg + '\n') 42 err(usage) 43 sys.exit(2) 44 for o, a in opts: 45 if o == '-i': 46 new_interpreter = a 47 if not new_interpreter or new_interpreter[0] != '/' or not args: 48 err('-i option or file-or-directory missing\n') 49 err(usage) 50 sys.exit(2) 51 bad = 0 52 for arg in args: 53 if os.path.isdir(arg): 54 if recursedown(arg): bad = 1 55 elif os.path.islink(arg): 56 err(arg + ': will not process symbolic links\n') 57 bad = 1 58 else: 59 if fix(arg): bad = 1 60 sys.exit(bad) 61 62ispythonprog = re.compile('^[a-zA-Z0-9_]+\.py$') 63def ispython(name): 64 return ispythonprog.match(name) >= 0 65 66def recursedown(dirname): 67 dbg('recursedown(%r)\n' % (dirname,)) 68 bad = 0 69 try: 70 names = os.listdir(dirname) 71 except os.error, msg: 72 err('%s: cannot list directory: %r\n' % (dirname, msg)) 73 return 1 74 names.sort() 75 subdirs = [] 76 for name in names: 77 if name in (os.curdir, os.pardir): continue 78 fullname = os.path.join(dirname, name) 79 if os.path.islink(fullname): pass 80 elif os.path.isdir(fullname): 81 subdirs.append(fullname) 82 elif ispython(name): 83 if fix(fullname): bad = 1 84 for fullname in subdirs: 85 if recursedown(fullname): bad = 1 86 return bad 87 88def fix(filename): 89## dbg('fix(%r)\n' % (filename,)) 90 try: 91 f = open(filename, 'r') 92 except IOError, msg: 93 err('%s: cannot open: %r\n' % (filename, msg)) 94 return 1 95 line = f.readline() 96 fixed = fixline(line) 97 if line == fixed: 98 rep(filename+': no change\n') 99 f.close() 100 return 101 head, tail = os.path.split(filename) 102 tempname = os.path.join(head, '@' + tail) 103 try: 104 g = open(tempname, 'w') 105 except IOError, msg: 106 f.close() 107 err('%s: cannot create: %r\n' % (tempname, msg)) 108 return 1 109 rep(filename + ': updating\n') 110 g.write(fixed) 111 BUFSIZE = 8*1024 112 while 1: 113 buf = f.read(BUFSIZE) 114 if not buf: break 115 g.write(buf) 116 g.close() 117 f.close() 118 119 # Finishing touch -- move files 120 121 # First copy the file's mode to the temp file 122 try: 123 statbuf = os.stat(filename) 124 os.chmod(tempname, statbuf[ST_MODE] & 07777) 125 except os.error, msg: 126 err('%s: warning: chmod failed (%r)\n' % (tempname, msg)) 127 # Then make a backup of the original file as filename~ 128 try: 129 os.rename(filename, filename + '~') 130 except os.error, msg: 131 err('%s: warning: backup failed (%r)\n' % (filename, msg)) 132 # Now move the temp file to the original file 133 try: 134 os.rename(tempname, filename) 135 except os.error, msg: 136 err('%s: rename failed (%r)\n' % (filename, msg)) 137 return 1 138 # Return success 139 return 0 140 141def fixline(line): 142 if not line.startswith('#!'): 143 return line 144 if "python" not in line: 145 return line 146 return '#! %s\n' % new_interpreter 147 148if __name__ == '__main__': 149 main() 150