shutil — Operaciones de archivo de alto nivel

Propósito:Operaciones de archivo de alto nivel.

El módulo shutil incluye operaciones de archivos de alto nivel como copiar y archivar.

Copiar archivos

copyfile() copia los contenidos de la fuente al destino y genera IOError si no tiene permiso para escribir el archivo de destino.

shutil_copyfile.py
import glob
import shutil

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

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

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

Debido a que la función abre el archivo de entrada para la lectura, independientemente de su tipo, los archivos especiales (como los nodos de dispositivos Unix) no se pueden copiar como nuevos archivos especiales con copyfile().

$ python3 shutil_copyfile.py

BEFORE: ['shutil_copyfile.py']
AFTER: ['shutil_copyfile.py', 'shutil_copyfile.py.copy']

La implementación de copyfile() usa la función de bajo nivel copyfileobj(). Mientras que los argumentos para copyfile() son los nombres de archivo, los argumentos para copyfileobj() son identificadores de archivos abiertos. El tercer argumento opcional es una longitud de buffer para usar leyendo en bloques.

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('read({}) got {} bytes'.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('Default:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output)

print()

print('All at once:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output, -1)

print()

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

El comportamiento predeterminado es leer usando bloques grandes. Utiliza -1 para leer toda la entrada en un momento u otro entero positivo para establecer una tamaño específico del bloque. Este ejemplo usa varios tamaños de bloques diferentes para mostrar el efecto.

$ python3 shutil_copyfileobj.py

Default:
read(16384) got 166 bytes
read(16384) got 0 bytes

All at once:
read(-1) got 166 bytes
read(-1) got 0 bytes

Blocks of 256:
read(256) got 166 bytes
read(256) got 0 bytes

La función copy() interpreta el nombre de salida como la herramienta Unix de línea de comandos cp. Si el destino mencionado se refiere a un directorio en lugar de un archivo, se crea un nuevo archivo en el directorio utilizando el nombre base de la fuente.

shutil_copy.py
import glob
import os
import shutil

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

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

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

Los permisos del archivo se copian junto con los contenidos.

$ python3 shutil_copy.py

BEFORE: []
AFTER : ['example/shutil_copy.py']

copy2() funciona como copy(), pero incluye los tiempos de acceso y de modificación en los meta datos copiados al nuevo archivo.

shutil_copy2.py
import os
import shutil
import time


def show_file_info(filename):
    stat_info = os.stat(filename)
    print('  Mode    :', oct(stat_info.st_mode))
    print('  Created :', time.ctime(stat_info.st_ctime))
    print('  Accessed:', time.ctime(stat_info.st_atime))
    print('  Modified:', time.ctime(stat_info.st_mtime))


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

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

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

El nuevo archivo tiene todas las mismas características que la versión anterior.

$ python3 shutil_copy2.py

SOURCE:
  Mode    : 0o100644
  Created : Wed Dec 28 19:03:12 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:12 2016
DEST:
  Mode    : 0o100644
  Created : Wed Dec 28 19:03:49 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:12 2016

Copiar meta datos de archivos

Por defecto, cuando se crea un nuevo archivo bajo Unix, éste recibe permisos basados en la umask del usuario actual. Para copiar los permisos de un archivo a otro, usa copymode().

shutil_copymode.py
import os
import shutil
import subprocess

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

print('BEFORE:', oct(os.stat('file_to_change.txt').st_mode))

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

print('AFTER :', oct(os.stat('file_to_change.txt').st_mode))

Este script de ejemplo crea un archivo para ser modificado, luego usa copymode() para duplicar los permisos del script al archivo de ejemplo

$ python3 shutil_copymode.py

BEFORE: 0o100444
AFTER : 0o100644

Para copiar otros meta datos sobre el archivo usa copystat().

shutil_copystat.py
import os
import shutil
import time


def show_file_info(filename):
    stat_info = os.stat(filename)
    print('  Mode    :', oct(stat_info.st_mode))
    print('  Created :', time.ctime(stat_info.st_ctime))
    print('  Accessed:', time.ctime(stat_info.st_atime))
    print('  Modified:', time.ctime(stat_info.st_mtime))


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

print('BEFORE:')
show_file_info('file_to_change.txt')

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

print('AFTER:')
show_file_info('file_to_change.txt')

Solo se duplican los permisos y las fechas asociadas al archivo con copystat().

$ python3 shutil_copystat.py

BEFORE:
  Mode    : 0o100444
  Created : Wed Dec 28 19:03:49 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:49 2016
AFTER:
  Mode    : 0o100644
  Created : Wed Dec 28 19:03:49 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:46 2016

Trabajar con árboles de directorio

shutil incluye tres funciones para trabajar con el directorio arboles. Para copiar un directorio de un lugar a otro, use copytree(). Ésta visita recursivamente el árbol de directorios de origen, copiando archivos al destino. El directorio de destino no debe existen de antemano.

shutil_copytree.py
import glob
import pprint
import shutil

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

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

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

El argumento symlinks controla si los enlaces simbólicos se copian como enlaces o como archivos. El valor predeterminado es copiar el contenido a nuevos archivos. Si la opción es verdadera, se crean nuevos enlaces simbólicos dentro del árbol de destino.

$ python3 shutil_copytree.py

BEFORE:
[]

AFTER:
['/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() acepta dos argumentos invocables para controlar su comportamiento. El argumento ignore se llama con el nombre de cada uno directorio o subdirectorio que se copia junto con una lista de los contenidos del directorio. Debe devolver una lista de elementos que debe ser copiado. El argumento copy_function se llama para copiar el archivo.

shutil_copytree_verbose.py
import glob
import pprint
import shutil


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


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

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

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

En el ejemplo, ignore_patterns() se usa para crear una función para omitir la copia de archivos fuente de Python. verbose_copy() imprime los nombres de los archivos a medida que se copian y luego usa copy2(), la función de copia por defecto, para realizar las copias.

$ python3 shutil_copytree_verbose.py

BEFORE:
[]

copying
 '../shutil/example.out'
 to '/tmp/example/example.out'
copying
 '../shutil/file_to_change.txt'
 to '/tmp/example/file_to_change.txt'
copying
 '../shutil/index.rst'
 to '/tmp/example/index.rst'

AFTER:
['/tmp/example/example',
 '/tmp/example/example.out',
 '/tmp/example/file_to_change.txt',
 '/tmp/example/index.rst']

Para eliminar un directorio y su contenido, usa rmtree().

shutil_rmtree.py
import glob
import pprint
import shutil

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

shutil.rmtree('/tmp/example')

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

Los errores se generan como excepciones de forma predeterminada, pero se pueden ignorar si el segundo argumento es verdadero, y una función especial de control de errores puede ser prevista en el tercer argumento.

$ python3 shutil_rmtree.py

BEFORE:
['/tmp/example/example',
 '/tmp/example/example.out',
 '/tmp/example/file_to_change.txt',
 '/tmp/example/index.rst']

AFTER:
[]

Para mover un archivo o directorio de un lugar a otro, use mover().

shutil_move.py
import glob
import shutil

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

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

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

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

Las semánticas son similares a las del comando mv de Unix. Si la fuente y el destino están dentro del mismo sistema de archivos, la fuente es renombrada. De lo contrario, la fuente se copia al destino y luego la fuente es eliminada

$ python3 shutil_move.py

BEFORE:  ['example.txt']
AFTER :  ['example.out']

Encontrar archivos

La función which() escanea una ruta de búsqueda buscando un archivo con nombre. El caso de uso típico es encontrar un programa ejecutable en la ruta de búsqueda del shell definida en la variable de entorno PATH.

shutil_which.py
import shutil

print(shutil.which('virtualenv'))
print(shutil.which('tox'))
print(shutil.which('no-such-program'))

Si no se puede encontrar un archivo que coincida con los parámetros de búsqueda, which() devuelve None.

$ python3 shutil_which.py

/Users/dhellmann/Library/Python/3.5/bin/virtualenv
/Users/dhellmann/Library/Python/3.5/bin/tox
None

which() toma argumentos para filtrar en función de los permisos que el archivo tiene, y la ruta de búsqueda para examinar. El argumento path es por defecto os.environ('PATH'), pero puede ser cualquier cadena que contenga nombres de directorio separados por os.pathsep. El argumento mode debe ser una máscara de bits que coincide con los permisos del archivo. Por defecto la máscara busca archivos ejecutables, pero el siguiente ejemplo utiliza una máscara de bits de archivo legible y una ruta de búsqueda alternativa para encontrar un archivo de configuración.

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)

Todavía hay un problema buscando archivos legibles de esta manera, porque en el tiempo entre encontrar el archivo y realmente intentar utilizarlo, el archivo puede ser eliminado o sus permisos pueden ser cambiados.

$ touch config.ini
$ python3 shutil_which_regular_file.py

./config.ini

Archivos

La biblioteca estándar de Python incluye muchos módulos para administrar el archivo archivos como tarfile y zipfile. También hay varias funciones de alto nivel para crear y extraer archivos en shutil. get_archive_formats() devuelve una secuencia de nombres y descripciones de los formatos soportados en el sistema actual.

shutil_get_archive_formats.py
import shutil

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

Los formatos admitidos dependen de qué módulos y bibliotecas subyacentes están disponibles, por lo que la salida de este ejemplo puede cambiar en función de donde se corre.

$ 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

Usa make_archive() para crear un nuevo archivo. Sus entradas son diseñadas para admitir mejor el archivado de un directorio completo y todos sus contenidos, recursivamente. Por defecto utiliza el directorio de trabajo actual, para que todos los archivos y subdirectorios aparezcan en el nivel superior del archivo. Para cambiar ese comportamiento, usa el argumento root_dir para pasar a una nueva posición relativa en el sistema de archivos y el argumento base_dir para especificar un directorio para agregar al archivo.

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('Creating archive:')
shutil.make_archive(
    'example', 'gztar',
    root_dir='..',
    base_dir='shutil',
    logger=logger,
)

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

Este ejemplo comienza dentro del directorio de origen para los ejemplos de shutil y sube un nivel en el sistema de archivos, luego agrega el directorio shutil a un archivo tar comprimido con gzip. El módulo logging está configurado para mostrar mensajes de make_archive() sobre lo que está haciendo.

$ python3 shutil_make_archive.py

Creating archive:
changing into '..'
Creating tar archive
changing back to '...'

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

shutil mantiene un registro de formatos que se pueden desempaquetar en el sistema actual, accesible a través de get_unpack_formats().

shutil_get_unpack_formats.py
import shutil

for format, exts, description in shutil.get_unpack_formats():
    print('{:<5}: {}, names ending in {}'.format(
        format, description, exts))

Este registro es diferente del registro para crear archivos porque también incluye extensiones de archivo comunes usadas para cada formato de modo que la función para extraer un archivo pueda adivinar qué formato utilizar en base a la extensión de archivo.

$ python3 shutil_get_unpack_formats.py

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

Extrae el archivo con unpack_archive(), pasando el nombre de archivo y opcionalmente el directorio donde debería ser extraído. Si no se da ningún directorio, se usa el directorio actual.

shutil_unpack_archive.py
import pathlib
import shutil
import sys
import tempfile

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

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

En este ejemplo, unpack_archive() es capaz de determinar el formato del archivo porque el nombre del archivo termina con tar.gz, y este el valor está asociado con el formato gztar en el registro de formato para desempaquetar.

$ python3 shutil_unpack_archive.py

Unpacking archive:

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

Espacio del sistema de archivos

Puede ser útil examinar el sistema de archivos local para ver cuánto espacio hay disponible antes de realizar una operación de larga duración que puede agota ese espacio. disk_usage() devuelve una tupla con el espacio total, la cantidad que se está utilizando actualmente y la cantidad que queda libre.

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('Total: {:6.2f} GB  {:6.2f} GiB'.format(
    total_b / gb, total_b / gib))
print('Used : {:6.2f} GB  {:6.2f} GiB'.format(
    used_b / gb, used_b / gib))
print('Free : {:6.2f} GB  {:6.2f} GiB'.format(
    free_b / gb, free_b / gib))

Los valores devueltos por disk_usage() son el número de bytes, por lo que el programa de ejemplo los convierte en unidades más legibles antes de imprimirlos.

$ python3 shutil_disk_usage.py

Total: 499.42 GB  465.12 GiB
Used : 246.68 GB  229.73 GiB
Free : 252.48 GB  235.14 GiB

Ver también