Skip to content

Instantly share code, notes, and snippets.

@dvarrazzo
Last active December 21, 2015 07:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dvarrazzo/6270281 to your computer and use it in GitHub Desktop.
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.
#!/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