2009-06-17 10 views
9

Esto parece una pregunta simple, pero no encontré nada similar aquí.Código de copia de archivo simple probado y verdadero en C?

Dado que no hay una función de copia de archivos en C, tenemos que aplicar la copia de archivo de nosotros mismos, pero no nos gusta reinventar la rueda incluso para cosas triviales como esa, así que me gustaría preguntar a la nube:

  1. ¿Qué código recomendaría usted para copiar archivos usando fopen()/fread()/fwrite()?
    • ¿Qué código recomendarías para copiar archivos usando open()/read()/write()?

Este código debe ser portátil (Windows/Mac/Linux/BSD/QNX/younameit), estable, la prueba del tiempo, rápido, eficiente de la memoria, etc. Entrar en partes internas del sistema específico para exprimir un poco más de rendimiento es bienvenido (como obtener el tamaño del clúster del sistema de archivos).

esto parece una pregunta trivial pero, por ejemplo, el código fuente para el comando CP no es 10 líneas de código C.

Respuesta

3

En cuanto a la E/S real va, el código que he escrito un millón de veces en diversas formas para copiar datos desde una secuencia a otra es algo como esto. Devuelve 0 en caso de éxito, o -1 con errno configurado en error (en cuyo caso podría haberse copiado cualquier número de bytes).

Tenga en cuenta que para copiar los archivos normales, puede omitir la materia EAGAIN, ya que los archivos regulares siempre están bloqueando E/S. Pero, inevitablemente, si escribe este código, alguien lo usará en otros tipos de descriptores de archivos, así que considérelo como regalo de promoción.

Hay una optimización específica de archivos que hace GNU cp, que no me he molestado aquí, que para bloques largos de 0 bytes en lugar de escribir simplemente extiende el archivo de salida buscando el extremo.

void block(int fd, int event) { 
    pollfd topoll; 
    topoll.fd = fd; 
    topoll.events = event; 
    poll(&topoll, 1, -1); 
    // no need to check errors - if the stream is bust then the 
    // next read/write will tell us 
} 

int copy_data_buffer(int fdin, int fdout, void *buf, size_t bufsize) { 
    for(;;) { 
     void *pos; 
     // read data to buffer 
     ssize_t bytestowrite = read(fdin, buf, bufsize); 
     if (bytestowrite == 0) break; // end of input 
     if (bytestowrite == -1) { 
      if (errno == EINTR) continue; // signal handled 
      if (errno == EAGAIN) { 
       block(fdin, POLLIN); 
       continue; 
      } 
      return -1; // error 
     } 

     // write data from buffer 
     pos = buf; 
     while (bytestowrite > 0) { 
      ssize_t bytes_written = write(fdout, pos, bytestowrite); 
      if (bytes_written == -1) { 
       if (errno == EINTR) continue; // signal handled 
       if (errno == EAGAIN) { 
        block(fdout, POLLOUT); 
        continue; 
       } 
       return -1; // error 
      } 
      bytestowrite -= bytes_written; 
      pos += bytes_written; 
     } 
    } 
    return 0; // success 
} 

// Default value. I think it will get close to maximum speed on most 
// systems, short of using mmap etc. But porters/integrators 
// might want to set it smaller, if the system is very memory 
// constrained and they don't want this routine to starve 
// concurrent ops of memory. And they might want to set it larger 
// if I'm completely wrong and larger buffers improve performance. 
// It's worth trying several MB at least once, although with huge 
// allocations you have to watch for the linux 
// "crash on access instead of returning 0" behaviour for failed malloc. 
#ifndef FILECOPY_BUFFER_SIZE 
    #define FILECOPY_BUFFER_SIZE (64*1024) 
#endif 

int copy_data(int fdin, int fdout) { 
    // optional exercise for reader: take the file size as a parameter, 
    // and don't use a buffer any bigger than that. This prevents 
    // memory-hogging if FILECOPY_BUFFER_SIZE is very large and the file 
    // is small. 
    for (size_t bufsize = FILECOPY_BUFFER_SIZE; bufsize >= 256; bufsize /= 2) { 
     void *buffer = malloc(bufsize); 
     if (buffer != NULL) { 
      int result = copy_data_buffer(fdin, fdout, buffer, bufsize); 
      free(buffer); 
      return result; 
     } 
    } 
    // could use a stack buffer here instead of failing, if desired. 
    // 128 bytes ought to fit on any stack worth having, but again 
    // this could be made configurable. 
    return -1; // errno is ENOMEM 
} 

Para abrir el archivo de entrada:

int fdin = open(infile, O_RDONLY|O_BINARY, 0); 
if (fdin == -1) return -1; 

de abrir el archivo de salida es tramposo. Como base, que quiere:

int fdout = open(outfile, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0x1ff); 
if (fdout == -1) { 
    close(fdin); 
    return -1; 
} 

pero hay factores de confusión:

  • lo que necesita especial de los casos cuando los archivos son los mismos, y no puedo recordar cómo hacerlo de forma portátil .
  • si el nombre del archivo de salida es un directorio, es posible que desee copiar el archivo en el directorio.
  • si el archivo de salida ya existe (abrir con O_EXCL para determinar esto y verificar si EEXIST está en error), es posible que desee hacer algo diferente, como lo hace cp -i.
  • es posible que desee que los permisos del archivo de salida reflejen los del archivo de entrada.
  • es posible que desee que se copien otros metadatos específicos de la plataforma.
  • es posible que desee o no desee desvincular el archivo de salida por error.

Obviamente las respuestas a todas estas preguntas podrían ser "hacer lo mismo que cp". En ese caso, la respuesta a la pregunta original es "ignorar todo lo que yo o alguien más haya dicho, y usar la fuente de cp".

Por cierto, obtener el tamaño del clúster del sistema de archivos es casi inútil. Casi siempre verá que la velocidad aumenta con el tamaño del búfer mucho después de haber superado el tamaño de un bloque de disco.

+0

Su muestra no puede compensar buf por cantidad ya escrita, lo que causará que las escrituras incompletas se reinicien desde la parte superior – Hasturkun

+0

Gracias. Siempre hay un error. –

+0

El OP solicitó una solución portátil, pero me parece que no funciona en Windows. Para empezar, 'poll()' falta, y 'ssize_t' es una extensión POSIX. No es insuperable, pero el código definitivamente no funciona como está. –

1

Aquí es un ejemplo muy sencillo y claro: Copy a file. Como está escrito en ANSI-C sin ninguna función particular, creo que este sería bastante portátil.

+2

Lamentablemente, utiliza fgetc que es bastante ineficiente. –

+0

¡Buen punto! Aunque es muy claro y portátil, definitivamente carece de rendimiento. – merkuro

+1

@David: ¿Es ineficaz fgetc()? Stdio hará su propio almacenamiento en búfer utilizando un búfer de tamaño BUFSIZ (8192 bytes en mi sistema).Si está utilizando MSVC++, #define _CRT_DISABLE_PERFCRIT_LOCKS en programas de un único subproceso. –

1

Dependiendo de lo que entendemos por la copia de un archivo, es sin duda lejos de ser trivial. Si se refiere a copiar solo el contenido, entonces no hay casi nada que hacer. Pero, en general, debe copiar los metadatos del archivo, y eso depende seguramente de la plataforma. No conozco ninguna biblioteca C que haga lo que quiera de manera portátil. Solo manejar el nombre del archivo por sí solo no es una cuestión trivial si le importa la portabilidad.

En C++, no es la biblioteca de archivos en boost

1

Una cosa que me encontré en la aplicación de mi propia copia de archivos, y parece obvio, pero no lo es: I S/son de lenta . Puedes medir la velocidad de tu copia por la cantidad de veces que haces. Entonces claramente necesitas hacer la menor cantidad posible.

Los mejores resultados que encontré fueron cuando obtuve un búfer de gran tamaño, leí todo el archivo fuente en una E/S, luego escribí todo el búfer en una E/S. Si incluso tuve que hacerlo en 10 lotes, fue muy lento. Tratar de leer y escribir cada byte, como un codificador de naive podría intentarlo primero, fue simplemente doloroso.

5

Esta es la función que utilizo cuando necesito copiar de un archivo a otro - con el instrumento de prueba:

/* 
@(#)File:   $RCSfile: fcopy.c,v $ 
@(#)Version:  $Revision: 1.11 $ 
@(#)Last changed: $Date: 2008/02/11 07:28:06 $ 
@(#)Purpose:  Copy the rest of file1 to file2 
@(#)Author:   J Leffler 
@(#)Modified:  1991,1997,2000,2003,2005,2008 
*/ 

/*TABSTOP=4*/ 

#include "jlss.h" 
#include "stderr.h" 

#ifndef lint 
/* Prevent over-aggressive optimizers from eliminating ID string */ 
const char jlss_id_fcopy_c[] = "@(#)$Id: fcopy.c,v 1.11 2008/02/11 07:28:06 jleffler Exp $"; 
#endif /* lint */ 

void fcopy(FILE *f1, FILE *f2) 
{ 
    char   buffer[BUFSIZ]; 
    size_t   n; 

    while ((n = fread(buffer, sizeof(char), sizeof(buffer), f1)) > 0) 
    { 
     if (fwrite(buffer, sizeof(char), n, f2) != n) 
      err_syserr("write failed\n"); 
    } 
} 

#ifdef TEST 

int main(int argc, char **argv) 
{ 
    FILE *fp1; 
    FILE *fp2; 

    err_setarg0(argv[0]); 
    if (argc != 3) 
     err_usage("from to"); 
    if ((fp1 = fopen(argv[1], "rb")) == 0) 
     err_syserr("cannot open file %s for reading\n", argv[1]); 
    if ((fp2 = fopen(argv[2], "wb")) == 0) 
     err_syserr("cannot open file %s for writing\n", argv[2]); 
    fcopy(fp1, fp2); 
    return(0); 
} 

#endif /* TEST */ 

Claramente, esta versión utiliza punteros de archivos de E/S estándar y no descriptores de archivo, pero es razonablemente eficiente y tan portátil como puede ser.


Bueno, excepto la función de error - que es propio de mí. Siempre que maneje los errores limpiamente, debería estar bien. El encabezado "jlss.h" declara fcopy(); el encabezado "stderr.h" declara err_syserr() entre muchas otras funciones similares de informe de errores. A continuación, se muestra una versión simple de la función: la real agrega el nombre del programa y hace otras cosas.

#include "stderr.h" 
#include <stdarg.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h> 

void err_syserr(const char *fmt, ...) 
{ 
    int errnum = errno; 
    va_list args; 
    va_start(args, fmt); 
    vfprintf(stderr, fmt, args); 
    va_end(args); 
    if (errnum != 0) 
     fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum)); 
    exit(1); 
} 

El código anterior se puede tratar como tener una licencia BSD moderno o GPL v3 a su elección.

+0

Me gusta, simple, limpio, funciona. Usé 4096 como mi BUFSIZ pero supongo que cualquier múltiplo de 512 debería funcionar bien. –

+0

@jonathan I este código ¿Cuál es el tamaño si BUFSIZ. ? Mi archivo de origen puede ser approxi 50 MB.? Entonces, ¿qué tamaño es bueno para mí? – user1089679

+0

BUFSIZ se define en '' y tiene un tamaño apropiado para los almacenamientos intermedios de archivos en la plataforma. Si desea hacerse cargo del tamaño del búfer, use un nombre diferente y especifique su tamaño: 'enum {BUFFER_SIZE = 4096};' o lo que quiera usar. Dentro de amplios límites, los tamaños de búfer más grandes son más rápidos, pero el aumento de 4 KiB a 256 KiB no suele ser tan grande, y debe sacrificar el espacio utilizado para el búfer. Dependiendo de su plataforma (servidor vs móvil, por ejemplo), puede sintonizar sus elecciones. 4 KiB a 64 KiB será adecuado para la mayoría de los propósitos. –

2

el tamaño de cada lectura necesita ser un múltiplo de 512 (tamaño del sector) 4096 es una buena

Cuestiones relacionadas