shutil - Operazioni di Alto Livello su File.

Scopo: Operazioni di alto livello su file.

Il modulo shutil include operazioni di alto livello su file tipo la copiatura e l'archiviazione.

Copiare i File

copyfile() copia il contenuto della sorgente nella destinazione e solleva IOError se non si possiedono i permessi per scrivere nel file di destinazione.

# shutil_copyfile.py

import glob
import shutil

print('PRIMA:', glob.glob('shutil_copyfile.*'))

shutil.copyfile('shutil_copyfile.py', 'shutil_copyfile.py.copy')

print('DOPO:', glob.glob('shutil_copyfile.*'))

Visto che la funzione apre il file di input per leggerlo, a prescindere dal tipo, i file speciali (tipo i nodi di dispositivo di Unix) non possono essere copiati come nuovi file speciali con copyfile().

$ python3 shutil_copyfile.py

PRIMA: ['shutil_copyfile.py']
DOPO: ['shutil_copyfile.py', 'shutil_copyfile.py.copy']

L'implementazione di copyfile() usa la funzione a basso livello copyfileobj(). Mentre gli argomenti per copyfile() sono nomi di file, quelli per copyfileobj() sono handle di file aperti. Il terzo argomento (opzionale) è la lunghezza del buffer da usare per leggere porzioni di file.

# shutil_copyfileobj.py

import io
import os
import shutil
import sys


class VerboseStringIO(io.StringIO):

    def read(self, n=-1):
        next = io.StringIO.read(self, n)
        print('letti({}) ottenuti {} byte'.format(n, len(next)))
        return next


lorem_ipsum = '''Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.  Vestibulum aliquam mollis dolor. Donec
vulputate nunc ut diam. Ut rutrum mi vel sem. Vestibulum
ante ipsum.'''

print('Predefinito:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output)

print()

print('Tutto in una volta:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output, -1)

print()

print('Blocchi di 256 byte:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output, 256)

Il comportamento predefinito è quello di leggere usando grandi blocchi. Si usa -1 per leggere tutto l'input in una volta oppure un intero positivo per impostare la dimensione del blocco desiderata. Questo esempio usa diverse dimensioni di blocco per mostrare l'effetto.

$ python3 shutil_copyfileobj.py

Predefinito:
letti(16384) ottenuti 166 byte
letti(16384) ottenuti 0 byte

Tutto in una volta:
letti(-1) ottenuti 166 byte
letti(-1) ottenuti 0 byte

Blocchi di 256 byte:
letti(256) ottenuti 166 byte
letti(256) ottenuti 0 byte

La funzione copy() interpreta il nome destinazione come lo strumento da riga di comando di Unix cp. Se la destinazione designata corrisponde a una directory invece che a un file, viene creato un nuovo file nella directory usando il nome base della sorgente.

# shutil_copy.py

import glob
import os
import shutil

os.mkdir('example')
print('PRIMA:', glob.glob('example/*'))

shutil.copy('shutil_copy.py', 'example')

print('DOPO :', glob.glob('example/*'))

I permessi per il file sono copiati assieme al contenuto.

$ python3 shutil_copy.py

PRIMA: []
DOPO : ['example/shutil_copy.py']

copy2() funziona come copy(), ma include data/ora di accesso e modifica nei meta-dati copiati nel nuovo file.

# shutil_copy2.py

import os
import shutil
import time


def show_file_info(filename):
    stat_info = os.stat(filename)
    print('  Modalità  :', oct(stat_info.st_mode))
    print('  Creato    :', time.ctime(stat_info.st_ctime))
    print('  Accesso   :', time.ctime(stat_info.st_atime))
    print('  Modificato:', time.ctime(stat_info.st_mtime))


os.mkdir('example')
print('SORGENTE:')
show_file_info('shutil_copy2.py')

shutil.copy2('shutil_copy2.py', 'example')

print('DESTINAZIONE:')
show_file_info('example/shutil_copy2.py')

Il nuovo file ha tutte le stesse caratteristiche della vecchia versione.

$ python3 shutil_copy2.py

SORGENTE:
  Modalità  : 0o100600
  Creato    : Fri Feb 17 22:37:07 2017
  Accesso   : Fri Feb 17 22:37:07 2017
  Modificato: Fri Feb 17 22:37:07 2017
DESTINAZIONE:
  Modalità  : 0o100600
  Creato    : Fri Feb 17 22:37:56 2017
  Accesso   : Fri Feb 17 22:37:07 2017
  Modificato: Fri Feb 17 22:37:07 2017

Copiare i Meta-dati del file

In modalità predefinita in Unix, quando viene creato un nuovo file, esso riceve i permessi in base alla umask dell'utente corrente. Per copiare i permessi da un file a un altro si usa copymode().

# shutil_copymode.py

import os
import shutil
import subprocess

with open('file_da_cambiare.txt', 'wt') as f:
    f.write('contenuto')
os.chmod('file_da_cambiare.txt', 0o444)

print('PRIMA:', oct(os.stat('file_da_cambiare.txt').st_mode))

shutil.copymode('shutil_copymode.py', 'file_da_cambiare.txt')

print('DOPO :', oct(os.stat('file_da_cambiare.txt').st_mode))

Questo script crea un file da modificare, poi usa copymode() per duplicare i permessi dello script al file di esempio.

$ python3 shutil_copymode.py

PRIMA: 0o100444
DOPO : 0o100600

Per copiare altri meta-dati per un file si usa copystat().

# shutil_copystat.py

import os
import shutil
import time


def show_file_info(filename):
    stat_info = os.stat(filename)
    print('  Modalità  :', oct(stat_info.st_mode))
    print('  Creato    :', time.ctime(stat_info.st_ctime))
    print('  Accesso   :', time.ctime(stat_info.st_atime))
    print('  Modificato:', time.ctime(stat_info.st_mtime))


with open('file_da_cambiare.txt', 'wt') as f:
    f.write('contenuto')
os.chmod('file_da_cambiare.txt', 0o444)

print('PRIMA:')
show_file_info('file_da_cambiare.txt')

shutil.copystat('shutil_copystat.py', 'file_da_cambiare.txt')

print('DOPO:')
show_file_info('file_da_cambiare.txt')

Con copystat(), solo i permessi e le date associate al file sono duplicati.

$ python3 shutil_copystat.py

PRIMA:
  Modalità  : 0o100444
  Creato    : Fri Feb 17 22:46:51 2017
  Accesso   : Fri Feb 17 22:46:22 2017
  Modificato: Fri Feb 17 22:46:51 2017
DOPO:
  Modalità  : 0o100600
  Creato    : Fri Feb 17 22:46:51 2017
  Accesso   : Fri Feb 17 22:46:50 2017
  Modificato: Fri Feb 17 22:46:50 2017

Lavorare con Alberi di Directory

Il modulo shutil comprende 3 funzioni per lavorare con alberi di directory. Per copiare una directory da un posto a un altro si usa copytree(). Attraversa ricorsivamente l'albero di directory sorgente, copiandone i file nella destinazione. La directory destinazione non deve essere già esistente.

# shutil_copytree.py

import glob
import pprint
import shutil

print('PRIMA:')
pprint.pprint(glob.glob('/tmp/example/*'))

shutil.copytree('../shutil', '/tmp/example')

print('\nDOPO:')
pprint.pprint(glob.glob('/tmp/example/*'))

L'argomento symlinks controlla se i collegamenti simbolici debbano essere copiati come collegamenti o come file. Il comportamento predefinito è di copiare il contenuto su nuovi file. Se l'opzione è True vengono creati dei nuovi collegamenti simbolici nella destinazione.

$ python3 shutil_copytree.py

PRIMA:
[]

DOPO:
['/tmp/example/example',
 '/tmp/example/example.out',
 '/tmp/example/file_to_change.txt',
 '/tmp/example/index.rst',
 '/tmp/example/shutil_copy.py',
 '/tmp/example/shutil_copy2.py',
 '/tmp/example/shutil_copyfile.py',
 '/tmp/example/shutil_copyfile.py.copy',
 '/tmp/example/shutil_copyfileobj.py',
 '/tmp/example/shutil_copymode.py',
 '/tmp/example/shutil_copystat.py',
 '/tmp/example/shutil_copytree.py',
 '/tmp/example/shutil_copytree_verbose.py',
 '/tmp/example/shutil_disk_usage.py',
 '/tmp/example/shutil_get_archive_formats.py',
 '/tmp/example/shutil_get_unpack_formats.py',
 '/tmp/example/shutil_make_archive.py',
 '/tmp/example/shutil_move.py',
 '/tmp/example/shutil_rmtree.py',
 '/tmp/example/shutil_unpack_archive.py',
 '/tmp/example/shutil_which.py',
 '/tmp/example/shutil_which_regular_file.py']

copytree() accetta due argomenti chiamabili per controllare il suo comportamento. L'argomento ignore viene chiamato con il nome di ciascuna directory o sotto directory che viene copiata, assieme a una lista del contenuto della directory. Dovrebbe ritornare un elenco di elementi che dovrebbero essere copiati. L'argomento copy_function viene chiamato per eseguire realmente la copia dei file.

# shutil_copytree_verbose.py

import glob
import pprint
import shutil


def verbose_copy(src, dst):
    print('in copia\n {!r}\n verso {!r}'.format(src, dst))
    return shutil.copy2(src, dst)


print('PRIMA:')
pprint.pprint(glob.glob('/tmp/example/*'))
print()

shutil.copytree(
    '../shutil', '/tmp/example',
    copy_function=verbose_copy,
    ignore=shutil.ignore_patterns('*.py'),
)

print('\nDOPO:')
pprint.pprint(glob.glob('/tmp/example/*'))

Nell'esempio, ignore_patterns() viene usata per creare una funzione chiamata come parametro per l'argomento ignore per evitare la copia dei file sorgente Python. verbose_copy() stampa i nomi dei file che sono copiati, quindi usa copy2(), la funzione di copia predefinita, per eseguire le copie.

$ python3 shutil_copytree_verbose.py

PRIMA:
[]

in copia
 '../shutil/index.rst'
 verso '/tmp/example/index.rst'
in copia
 '../shutil/example.out'
 verso '/tmp/example/example.out'
in copia
 '../shutil/file_to_change.txt'
 verso '/tmp/example/file_to_change.txt'
in copia
 '../shutil/example'
 verso '/tmp/example/example'
in copia
 '../shutil/shutil_copyfile.py.copy'
 verso '/tmp/example/shutil_copyfile.py.copy'

DOPO:
['/tmp/example/file_to_change.txt',
 '/tmp/example/shutil_copyfile.py.copy',
 '/tmp/example/example.out',
 '/tmp/example/example',
 '/tmp/example/index.rst']

Per eliminare una directory e il suo contenuto, si usa rmtree().

# shutil_rmtree.py

import glob
import pprint
import shutil

print('PRIMA:')
pprint.pprint(glob.glob('/tmp/example/*'))

shutil.rmtree('/tmp/example')

print('\nDOPO:')
pprint.pprint(glob.glob('/tmp/example/*'))

Gli errori sono sollevati come eccezioni in modalità predefinita e possono essere ignorati se il secondo argomento è True, e una funzione speciale per la gestione dell'errore può essere fornita come terzo parametro.

$ python3 shutil_rmtree.py

PRIMA:
['/tmp/example/file_to_change.txt',
 '/tmp/example/shutil_copyfile.py.copy',
 '/tmp/example/example.out',
 '/tmp/example/example',
 '/tmp/example/index.rst']

DOPO:
[]

Per spostare un file o una directory da un posto all'altro si usa move().

# shutil_move.py

import glob
import shutil

with open('example.txt', 'wt') as f:
    f.write('contents')

print('PRIMA: ', glob.glob('example*'))

shutil.move('example.txt', 'example.out')

print('DOPO : ', glob.glob('example*'))

La semantica è simile a quella del comando Unix mv. Se la sorgente e la destinazione sono all'interno dello stesso file system, la sorgente viene rinominata. Altrimenti la sorgente viene copiata nella destinazione, quindi la sorgente viene rimossa.

$ python3 shutil_move.py

PRIMA:  ['example.txt']
DOPO :  ['example.out']

Trovare File

La funzione which() esamina un percorso di ricerca per trovare un file. Il caso d'uso tipico è cercare un programma eseguibile nel percorso di ricerca della shell definito nella variabile di ambiente PATH.

# shutil_which.py

import shutil

print(shutil.which('virtualenv'))
print(shutil.which('pip'))
print(shutil.which('nessun-programma'))

Se non è stata trovata corrispondenza con i parametri di ricerca, which() ritorna None.

$ python3 shutil_which.py

/usr/local/bin/virtualenv
/usr/local/bin/pip
None

which()riceve gli argomenti da filtrare in base ai permessi che ha il file, e il percorso di ricerca da esaminare. L'argomento path è predefinito a os.environ('PATH'), ma può essere qualsiasi stringa che contenga nomi di directory separate da os.pathsep. L'argomento mode dovrebbe essere una bitmask che corrisponde ai permessi del file. Nella modalità predefinita la bitmask corrisponde a file eseguibili, ma l'esempio seguente usa una bitmask per cercare file leggibili e un percorso di ricerca alternativo per trovare un file di configurazione.

# shutil_which_regular_file.py

import os
import shutil

path = os.pathsep.join([
    '.',
    os.path.expanduser('~/pymotw'),
])

mode = os.F_OK | os.R_OK

filename = shutil.which(
    'config.ini',
    mode=mode,
    path=path,
)

print(filename)

Esiste comunque una race condition quando si cercano file leggibili in questo modo, in quanto nel tempo trascorso tra il cercare il file e il reale tentativo di usarlo, il file stesso potrebbe essere stato cancellato o i suoi permessi modificati.

$ touch config.ini
$ python3 shutil_which_regular_file.py

./config.ini

Archivi

La libreria standard di Python comprende molti moduli per gestire file di archivio come tarfile e zipfile. Ci sono anche parecchie funzioni di più alto livello per creare ed estrarre archivi in shutil. get_archive_formats() ritorna una sequenza di nomi e descrizioni per i formati supportati nel sistema corrente.

# shutil_get_archive_formats.py

import shutil

for format, description in shutil.get_archive_formats():
    print('{:<5}: {}'.format(format, description))

I formati supportati dipendono da quali moduli e librerie sottostanti sono disponibili, quindi il risultato di questo esempio può variare.

$ python3 shutil_get_archive_formats.py

bztar: bzip2'ed tar-file
gztar: gzip'ed tar-file
tar  : uncompressed tar file
xztar: xz'ed tar-file
zip  : ZIP file

Si usa made_archive() per creare un nuovo file archivio. I suoi input sono progettati per supportare al meglio l'archiviazione di una intera directory e del suo contenuto, ricorsivamente. Nella modalità predefinita usa la directory di lavoro corrente, in modo che tutti i suoi file e le sotto directory appaiano al livello superiore dell'archivio. Per modificare questo comportamento si usa l'argomento root_dir per spostarsi su di una nuova posizione relativa nel file system e base_dir per specificare una directory da aggiungere all'archivio.

# shutil_make_archive.py

import logging
import shutil
import sys
import tarfile

logging.basicConfig(
    format='%(message)s',
    stream=sys.stdout,
    level=logging.DEBUG,
)
logger = logging.getLogger('pymotw')

print('Creazione archivio:')
shutil.make_archive(
    'example', 'gztar',
    root_dir='..',
    base_dir='shutil',
    logger=logger,
)

print('\nContenuto archivio:')
with tarfile.open('example.tar.gz', 'r') as t:
    for n in t.getnames():
        print(n)

Questo esempio inizia all'interno della directory degli esempi per shutil e si sposta su di un livello nel file system, poi aggiunge la directory shutil a un archivio tar compresso con gzip. Il modulo logging è configurato per mostrare i messaggi da make_archive().

$ python3 shutil_make_archive.py
Creazione archivio:
changing into '..'
Creating tar archive
changing back to '...'

Contenuto archivio:
shutil
shutil/shutil_disk_usage.py
shutil/shutil_copyfileobj.py
shutil/index.rst
shutil/shutil_rmtree.py
shutil/shutil_copy2.py
shutil/shutil_get_archive_formats.py
shutil/shutil_which.py
shutil/shutil_copyfile.py
shutil/example.out
shutil/shutil_copytree_verbose.py
shutil/shutil_copymode.py
shutil/file_to_change.txt
shutil/example
shutil/shutil_move.py
shutil/shutil_copystat.py
shutil/shutil_copytree.py
shutil/shutil_which_regular_file.py
shutil/shutil_get_unpack_formats.py
shutil/shutil_copy.py
shutil/shutil_unpack_archive.py
shutil/shutil_copyfile.py.copy
shutil/shutil_make_archive.py

shutil mantene un registro dei formati nel sistema corrente, accessibili tramite get_unpack_formats().

# shutil_get_unpack_formats.py

import shutil

for format, exts, description in shutil.get_unpack_formats():
    print('{:<5}: {}, nomi che finiscono in {}'.format(
        format, description, exts))

Il registro è diverso da quello per la creazione degli archivi visto che include anche estensioni comuni di file utilizzate da ciascun formato in modo che la funzione per estrarre un archivio possa indovinare quale formato usare in base all'estensione del file.

$ python3 shutil_get_unpack_formats.py

bztar: bzip2'ed tar-file, nomi che finiscono in ['.tar.bz2', '.tbz2']
gztar: gzip'ed tar-file, nomi che finiscono in ['.tar.gz', '.tgz']
tar  : uncompressed tar file, nomi che finiscono in ['.tar']
xztar: xz'ed tar-file, nomi che finiscono in ['.tar.xz', '.txz']
zip  : ZIP file, nomi che finiscono in ['.zip']

L'archivio si estrae con unpack_archive() passando il nome del file archivio e opzionalmente la directory nella quale dovrebbe avvenire l'estrazione. Se nessuna directory viene passata, viene usata la directory corrente.

# shutil_unpack_archive.py

import pathlib
import shutil
import sys
import tempfile

with tempfile.TemporaryDirectory() as d:
    print('Estrazione archivio:')
    shutil.unpack_archive(
        'example.tar.gz',
        extract_dir=d,
    )

    print('\nCreato:')
    prefix_len = len(d) + 1
    for extracted in pathlib.Path(d).rglob('*'):
        print(str(extracted)[prefix_len:])

In questo esempio unpack_archive() è in grado di determinare il formato dell'archivio visto che il nome del file finisce per tar.gz, e quel valore è associato al formato gztar nel registro dei formati di estrazione.

$ python3 shutil_unpack_archive.py

Estrazione archivio:

Creato:
shutil
shutil/shutil_which.py
shutil/shutil_copy.py
shutil/shutil_get_archive_formats.py
shutil/shutil_unpack_archive.py
shutil/file_to_change.txt
shutil/shutil_copytree_verbose.py
shutil/shutil_copyfile.py.copy
shutil/shutil_move.py
shutil/shutil_copystat.py
shutil/example.out
shutil/example
shutil/shutil_rmtree.py
shutil/shutil_disk_usage.py
shutil/shutil_make_archive.py
shutil/shutil_copyfile.py
shutil/shutil_copymode.py
shutil/shutil_copy2.py
shutil/shutil_which_regular_file.py
shutil/shutil_copytree.py
shutil/shutil_get_unpack_formats.py
shutil/index.rst
shutil/shutil_copyfileobj.py

Spazio nel File System

Può essere utile esaminare il file system locale per vedere quanto spazio sia disponibile prima di eseguire una operazione che richiede molto tempo che potrebbe esaurire quello spazio. disk_usage() ritorna una tupla con lo spazio totale, quello attualmente in uso, e il rimanente libero.

# shutil_disk_usage.py

import shutil

total_b, used_b, free_b = shutil.disk_usage('.')

gib = 2 ** 30  # GiB == gibibyte
gb = 10 ** 9   # GB == gigabyte

print('Totale: {:6.2f} GB  {:6.2f} GiB'.format(
    total_b / gb, total_b / gib))
print('Usato : {:6.2f} GB  {:6.2f} GiB'.format(
    used_b / gb, used_b / gib))
print('Libero: {:6.2f} GB  {:6.2f} GiB'.format(
    free_b / gb, free_b / gib))

I valori ritornati da disk_usage() sono in byte, quindi il programma di esempio converte i valori in una unità di misura maggiormente leggibile prima di stamparli.

$ python3 shutil_disk_usage.py

Totale:  19.55 GB   18.21 GiB
Usato :  16.70 GB   15.55 GiB
Libero:   1.84 GB    1.71 GiB

Vedere anche:

shutil
La documentazione della libreria standard per questo modulo.