在C on linux上你将如何实现cp

时间:2015-11-18 23:50:37

标签: c linux copy

我发现谷歌代码超过50行,这对我正在尝试的事情完全没有必要。

我想在C中进行一个非常简单的cp实现。

这样我就可以使用缓冲区大小,看看它如何影响性能。

我想只使用像read()write()这样的Linux API调用,但我没有运气。

我想要一个定义为特定大小的缓冲区,因此来自file1的数据可以读入缓冲区然后写入file2,并一直持续到file1达到EOF。

这是我尝试的但它没有做任何事情

#include <stdio.h>
#include <sys/types.h>

#define BUFSIZE 1024

int main(int argc, char* argv[]){

    FILE fp1, fp2;
    char buf[1024];
    int pos;


    fp1 = open(argv[1], "r");
    fp2 = open(argv[2], "w");

    while((pos=read(fp1, &buf, 1024)) != 0)
    {
        write(fp2, &buf, 1024);
    }


    return 0;
}

它的工作方式是./mycopy file1.txt file2.txt

4 个答案:

答案 0 :(得分:3)

这段代码有一个重要的问题,即无论你阅读了多少,你总是写1024个字节。

此外:

  1. 您不检查命令行参数的数量。
  2. 您不检查源文件是否存在(如果它已打开)。
  3. 您不会检查目标文件是否已打开(权限问题)。
  4. 您将与第一个元素的指针类型不同的数组的地址传递给数组。
  5. fp1 的类型以及fp2的类型错误。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    #include <fcntl.h>
    
    int main(int argc, char **argv)
    {
        char buffer[1024];
        int files[2];
        ssize_t count;
    
        /* Check for insufficient parameters */
        if (argc < 3)
            return -1;
        files[0] = open(argv[1], O_RDONLY);
        if (files[0] == -1) /* Check if file opened */
            return -1;
        files[1] = open(argv[2], O_WRONLY | O_CREAT | S_IRUSR | S_IWUSR);
        if (files[1] == -1) /* Check if file opened (permissions problems ...) */
        {
            close(files[0]);
            return -1;
        }
    
        while ((count = read(files[0], buffer, sizeof(buffer))) != 0)
            write(files[1], buffer, count);
    
        return 0;
    }
    

答案 1 :(得分:1)

转到K&amp; R“The C Programming Language”的第8.3节。在那里,您将看到一个您想要完成的示例。尝试使用不同的缓冲区大小,最终会看到性能最高的点。

答案 2 :(得分:1)

好问题。与另一个好问题相关:

How can I copy a file on Unix using C?

cp 的“最简单”实现有两种方法。一种方法使用某种文件复制系统调用函数——这是我们得到的最接近 Unix cp 命令的 C 函数版本的东西。另一种方法使用缓冲区和读/写系统调用函数,可以直接使用,也可以使用 FILE 包装器。

仅在内核拥有的内存中发生的文件复制系统调用可能比在内核和用户拥有的内存中发生的系统调用快,尤其是在网络文件系统设置中(在机器之间复制)。但这需要测试(例如使用 Unix 命令 time),并且将取决于编译和执行代码的硬件。

操作系统没有标准 Unix 库的人也可能想要使用您的代码。然后你想使用缓冲区读/写版本,因为它只依赖于 (和朋友)。

这是一个使用 unix 标准库 copy_file_range 中的函数 <unistd.h> 将源文件复制到(可能不存在的)目标文件的示例。复制发生在内核空间。

/* copy.c
 *
 * Defines function copy:
 *
 * Copy source file to destination file on the same filesystem (possibly NFS).
 * If the destination file does not exist, it is created. If the destination
 * file does exist, the old data is truncated to zero and replaced by the 
 * source data. The copy takes place in the kernel space.
 *
 * Compile with:
 *
 * gcc copy.c -o copy -Wall -g
 */

#define _GNU_SOURCE 
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

/* On versions of glibc < 2.27, need to use syscall.
 * 
 * To determine glibc version used by gcc, compute an integer representing the
 * version. The strides are chosen to allow enough space for two-digit 
 * minor version and patch level.
 *
 */
#define GCC_VERSION (__GNUC__*10000 + __GNUC_MINOR__*100 + __gnuc_patchlevel__)
#if GCC_VERSION < 22700
static loff_t copy_file_range(int in, loff_t* off_in, int out, 
  loff_t* off_out, size_t s, unsigned int flags)
{
  return syscall(__NR_copy_file_range, in, off_in, out, off_out, s,
    flags);
}
#endif

/* The copy function.
 */
int copy(const char* src, const char* dst){
  int in, out;
  struct stat stat;
  loff_t s, n;
  if(0>(in = open(src, O_RDONLY))){
    perror("open(src, ...)");
    exit(EXIT_FAILURE);
  }
  if(fstat(in, &stat)){
    perror("fstat(in, ...)");
    exit(EXIT_FAILURE);
  }
  s = stat.st_size; 
  if(0>(out = open(dst, O_CREAT|O_WRONLY|O_TRUNC, 0644))){
    perror("open(dst, ...)");
    exit(EXIT_FAILURE);
  }
  do{
    if(1>(n = copy_file_range(in, NULL, out, NULL, s, 0))){
      perror("copy_file_range(...)");
      exit(EXIT_FAILURE);
    }
    s-=n;
  }while(0<s && 0<n);
  close(in);
  close(out);
  return EXIT_SUCCESS;
}

/* Test it out.
 *
 * BASH:
 *
 * gcc copy.c -o copy -Wall -g
 * echo 'Hello, world!' > src.txt
 * ./copy src.txt dst.txt
 * [ -z "$(diff src.txt dst.txt)" ]
 *
 */

int main(int argc, char* argv[argc]){
  if(argc!=3){
    printf("Usage: %s <SOURCE> <DESTINATION>", argv[0]);
    exit(EXIT_FAILURE);
  }
  copy(argv[1], argv[2]);
  return EXIT_SUCCESS;
}

它基于我的 Ubuntu 20.x Linux 发行版的 copy_file_range 手册页中的示例。使用以下命令检查您的手册页:

> man copy_file_range

然后点击 jEnter,直到您进入示例部分。或输入 /example 进行搜索。

/

这是一个仅使用 stdlib/stdio 的示例。缺点是它在用户空间中使用了一个中间缓冲区。

/* copy.c
 *
 * Compile with:
 * 
 * gcc copy.c -o copy -Wall -g
 *
 * Defines function copy:
 *
 * Copy a source file to a destination file. If the destination file already
 * exists, this clobbers it. If the destination file does not exist, it is
 * created. 
 *
 * Uses a buffer in user-space, so may not perform as well as 
 * copy_file_range, which copies in kernel-space.
 *
 */

#include <stdlib.h>
#include <stdio.h>

#define BUF_SIZE 65536 //2^16

int copy(const char* in_path, const char* out_path){
  size_t n;
  FILE* in=NULL, * out=NULL;
  char* buf = calloc(BUF_SIZE, 1);
  if((in = fopen(in_path, "rb")) && (out = fopen(out_path, "wb")))
    while((n = fread(buf, 1, BUF_SIZE, in)) && fwrite(buf, 1, n, out));
  free(buf);
  if(in) fclose(in);
  if(out) fclose(out);
  return EXIT_SUCCESS;
}

/* Test it out.
 *
 * BASH:
 *
 * gcc copy.c -o copy -Wall -g
 * echo 'Hello, world!' > src.txt
 * ./copy src.txt dst.txt
 * [ -z "$(diff src.txt dst.txt)" ]
 *
 */
int main(int argc, char* argv[argc]){
  if(argc!=3){
    printf("Usage: %s <SOURCE> <DESTINATION>\n", argv[0]);
    exit(EXIT_FAILURE);
  }
  return copy(argv[1], argv[2]);
}

在仍然使用类 Unix 的 C API 的同时确保总体可移植性的另一种方法是使用 GNOME(例如 GLib、GIO)进行开发

https://docs.gtk.org/glib/ https://docs.gtk.org/gio/

答案 3 :(得分:0)

#include <stdio.h>
int cpy(char *, char *);
int main(int argc, char *argv[])
{
    char *fn1 = argv[1];
    char *fn2 = argv[2];

    if (cpy(fn2, fn1) == -1) {
        perror("cpy");
        return 1;
    }
    reurn 0;
}
int cpy(char *fnDest, char *fnSrc)
{
    FILE *fpDest, *fpSrc;
    int c;

    if ((fpDest = fopen(fnDest, "w")) && (fpSrc = fopen(fnSrc, "r"))) {
        while ((c = getc(fpSrc)) != EOF)
            putc(fpDest);
        fclose(fpDest);
        fclose(fpSrc);

        return 0;
    }
    return -1;
}

首先,我们从命令行(argv[1]argv[2])获取两个文件名。我们不从*argv开始的原因是它包含程序名称。

然后我们调用我们的cpy函数,它将第二个命名文件的内容复制到第一个命名文件的内容中。

cpy内,我们声明了两个文件指针:fpDest,目标文件指针,fpSrc,源文件指针。我们还声明了c,即将被阅读的字符。它的类型为int,因为EOF不适合char

如果我们可以成功打开文件(如果fopen未返回NULL),我们会从fpSrc获取字符并将其复制到fpDest,只要我们读过的字符不是EOF。一旦我们看到EOF,我们关闭文件指针,并返回0,即成功指标。如果我们无法打开文件,则会返回-1。调用者可以检查-1的返回值,如果是,则打印错误消息。