Last active
December 21, 2015 07:19
-
-
Save dvarrazzo/6270281 to your computer and use it in GitHub Desktop.
A script for smart file rename. Written several years ago, not necessarily the best tool for the job.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
'''reren -- rinomina usando espressioni regolari. | |
Utilizzo: | |
reren "from" "to" [dir] [-y] [-s] [-f] | |
from: Pattern a cui devono corrispondere i file. I file vengono letti | |
in ordine alfabetico. | |
to: Stringa in cui vanno trasformati i file; | |
se viene specificata l'opzione -f, deve essere una funzione | |
che prende una stringa e restituisce una stringa. Ad essa vengono | |
passati i pattern trovati nei nomi e il risultato viene sostituito | |
ad essi. | |
dir: Cartella contenente i file. Se omessa viene utilizzata | |
la cartella corrente. L'ultima parte del path puo' contenere | |
i caratteri * e ? per filtrare i file da rinominare. Utile quando | |
il pattern indica un pezzo dei nomi dei file e non un sottoinsieme | |
dei file (come negli esempi 2-5) | |
-y Evita di chiedere conferma ad ogni file. | |
-s Simulazione: le azioni vengono visualizzate ma non effettuate. | |
-f Specifica che l'argomento "to" e' una funzione. | |
Esempio: | |
1. reren "(?i)(\\d\\d)-(.*).mp3" "\\1 - \\2.mp3" | |
01-Smells_like_teen_spirits.MP3 -> 01 - Smells_like_teen_spirits.mp3 | |
2. reren "_" " " | |
01 - Smells_like_teen_spirits.mp3 -> 01 - Smells like teen spirits.mp3 | |
3. reren " ." string.upper -f | |
01 - Smells like teen spirits.mp3 -> 01 - Smells Like Teen Spirits.mp3 | |
4. reren "^\\d\\d" "lambda s: '%02d' % (int(s)+5)" -f | |
01 - Smells like teen spirits.mp3 -> 06 - Smells like teen spirits.mp3 | |
Oltre alle funzioni standard Python c'e' la funzione counter() che | |
restituisce un numero crescente a partire da 0 ogni volta che viene | |
richiamata. | |
5. reren "(\d{4})" "lambda x: '%04d' % (counter() * 4 + 2)" -f | |
DSCN0025.JPG -> DSCN0002.JPG | |
DSCN0027.JPG -> DSCN0006.JPG | |
DSCN0028.JPG -> DSCN0010.JPG | |
DSCN0029.JPG -> DSCN0014.JPG | |
''' | |
import sys | |
import os | |
import os.path | |
import re | |
import string | |
def reRen(args): | |
"""Interpreta un dizionario di opzioni e rinomina i file. | |
Le opzioni riconosciute sono: | |
path : directory contenente i file da modificare; | |
from : stringa alla quale devono corrisponderei file da rinominare | |
to : stringa nella quale devono essere rinominati i file | |
files : lista dei file da rinominare | |
confirm : vale True se si vuole una conferma dei file da rinominare | |
sim : vale True se si vuole la sola simulazione | |
fun : vale True se "to" e' una funzione callable | |
""" | |
# stampa tutti gli argomenti passati alla routine: utile per il debug | |
## for (k, v) in args.items(): | |
## print k, ":", v | |
r = re.compile(args['from']) | |
# repl e' l'argomento di sostituzione del pattern. | |
# se -f non e' specificato vale la stringa args['to'] | |
# altrimenti e' una funzione : match -> string che consente | |
# di applicare la funzione args['to'], che prende come input | |
# una stringa, all'oggetto match, come chiesto dalla re.sub | |
repl = args['fun'] and (lambda m: args['to'](m.group())) or args['to'] | |
for fileOld in args['files']: | |
if r.search(fileOld): | |
# il file matcha con quello che cerco | |
# se si verificano errori nella sostituzione, interrompe il | |
# programma | |
try: | |
fileNew = r.sub(repl, fileOld) | |
except: | |
print fileOld, "- errore nella sostituzione:" | |
print sys.exc_info()[1] | |
return | |
print fileOld, "->", fileNew | |
answer = (args['confirm'] and not args['sim']) \ | |
and userPrompt() or 'y' | |
if answer == 'a': | |
answer = 'y' | |
args['confirm'] = False | |
if answer == 'y': | |
if not args['sim']: | |
os.rename(os.path.join(args['path'], fileOld), | |
os.path.join(args['path'], fileNew)) | |
elif answer == 'q': | |
break | |
def userPrompt(): | |
answer = 'x' | |
while answer not in 'ynaq': | |
answer = (raw_input('[Yes, No, All, Quit]? ') + 'x')[0].lower() | |
return answer | |
class ParseError(Exception): | |
def __init__(self, value): | |
self.value = value | |
def __str__(self): | |
return `self.value` | |
def parseArgs(): | |
"""Legge la riga di comandi e restituisce un dictionary delle opzioni | |
se la riga di comando non e' valida, restituisce None | |
""" | |
argv = sys.argv[:] | |
argv.pop(0) | |
# se non ci sono abbastanza argomenti, esci. | |
if len(argv) < 2: | |
return None | |
#risultato da restituire se tutto va bene | |
argd = {} | |
#so che questi due argomenti ci sono | |
argd['from'] = argv.pop(0) | |
argd['to'] = argv.pop(0) | |
# controlla se l'argomento successivo e' una directory valida | |
argd['path'] = os.getcwd() | |
filesPattern = '' | |
if argv: | |
if os.path.exists(argv[0]): | |
argd['path'] = argv.pop(0) | |
else: | |
# potrei avere un path valido seguito da wildcard | |
pathHead, pathTail = os.path.split(argv[0]) | |
if os.path.exists(pathHead) or \ | |
(pathHead == '' and re.search(r'\*|\?', pathTail)): | |
argv.pop(0) | |
if pathHead: argd['path'] = pathHead | |
# il nome del file con le wildcard dev'essere convertito | |
# in espressione regolare. | |
filesPattern = filesFilterToPattern(pathTail) | |
argd['files'] = filter( \ | |
lambda f: os.path.isfile(os.path.join(argd['path'], f)), | |
os.listdir(argd['path'])) | |
# se sono state utilizzate wildcard nel path, occorre processare i soli | |
# file che corrispondono al pattern filesPattern | |
if filesPattern: | |
argd['files'] = filter( \ | |
lambda f: re.match(filesPattern, f, re.I), # non case sensitive | |
# irrigidire x linux | |
argd['files']) | |
#il resto delle opzioni... | |
argd['confirm'] = '-y' not in [s.lower() for s in argv] | |
argd['sim'] = '-s' in [s.lower() for s in argv] | |
argd['fun'] = '-f' in [s.lower() for s in argv] | |
# se viene specificata l'opzione -f, to va interpretato | |
# come una funzione f : string -> string | |
try: | |
if argd['fun']: | |
argd['to'] = eval(argd['to']) | |
if not callable(argd['to']): | |
raise ParseError("L'argomento 'to' non e' callable") | |
except: | |
print "\nErrore: occorre indicare una funzione dopo l'argomento -f\n" | |
return None | |
return argd | |
def filesFilterToPattern(filesFilter): | |
"""Converte un nome di file con wildcard * e ? in un pattern | |
per riconoscere gli stessi file con un'espressione regolare""" | |
# "escapa" i caratteri validi in un nome di file ma non | |
# in un pattern. | |
toBeEscaped = ['.', '$', '^', '+', '[', ']', '(', ')'] | |
filesFilter = re.sub( \ | |
'(\\' + '|\\'.join(toBeEscaped) + ')', | |
r'\\\1', filesFilter) | |
#sostituisce * con .* e ? con . | |
filesFilter = re.sub(r'\*', r'.*', filesFilter) | |
filesFilter = re.sub(r'\?', r'.', filesFilter) | |
# il file deve corrispondere a tutto il pattern. Concludo il pattern | |
# con un carattere di fine sequenza. | |
return filesFilter + '$' | |
def counter(): | |
if 'sequence_step' not in globals(): | |
globals()['sequence_step'] = 0 | |
globals()['sequence_step'] += 1 | |
return globals()['sequence_step'] - 1 | |
if __name__ == '__main__': | |
args = parseArgs() | |
if args: | |
reRen(args) | |
else: | |
print __doc__ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment