cp指令

简介

cp命令用来将一个或多个源文件或者目录复制到指定的目的文件或目录。它可以将单个源文件复制成一个指定文件名的具体的文件或一个已经存在的目录下。cp命令还支持同时复制多个文件,当一次复制多个文件时,目标文件参数必须是一个已经存在的目录,否则将出现错误。

语法

cp [选项] [参数]

选项
-a:此参数的效果和同时指定"-dpR"参数相同;
-d:当复制符号连接时,把目标文件或目录也建立为符号连接,并指向与源文件或目录连接的原始文件或目录;
-f:强行复制文件或目录,不论目标文件或目录是否已存在;
-i:覆盖既有文件之前先询问用户;
-l:对源文件建立硬连接,而非复制文件;
-p:保留源文件或目录的属性;
-R/r:递归处理,将指定目录下的所有文件与子目录一并处理;
-s:对源文件建立符号连接,而非复制文件;
-u:使用这项参数后只会在源文件的更改时间较目标文件更新时或是名称相互对应的目标文件并不存在时,才复制文件;
-S:在备份文件时,用指定的后缀“SUFFIX”代替文件的默认后缀;
-b:覆盖已存在的文件目标前将目标文件备份;
-v:详细显示命令执行的操作。
参数
- 源文件:指定源文件列表。默认情况下,cp命令不能复制目录,如果要复制目录,则需要加 [-R] 选项。
- 目标文件:指定目标文件。当源文件为多个时,要求指定 “目标文件” 为目录。
实例

如果把一个文件复制到目标文件中,而目标文件已经存在。那么该目标文件的内容将被清空。此命令中的路径参数既可以是绝对路径,也可以是相对路径。通常会用到 ‘.’ 或 ‘..’ 的形式,例如下面的命令是将指定文件复制到当前目录下。

cp ../code/mycp.c ./

如果没有文件的复相关权限,则命令会报错。
将 test1 复制到 /home/jelly 目录下并改名为 test2:

cp test1 /home/jelly/test2

我们在 linux 下复制文件的时候,有时候会覆盖同名文件,此时会有选项 [Y/n] 让我们选择是否需要覆盖对应文件。

cp命令执行拓扑图

这里写图片描述

命令核心代码

cp命令的核心点之一是拷贝目录文件。对目录文件的拷贝,实际上是对该目录下的所有文件的拷贝。显然,这是一个递归的过程。

我们循环读取目录下的每个文件,对于 ‘.’ 和 ‘..’ 不做任何处理,将读到的其它文件作为新的源文件(记得要拼接新的源文件路径),然后针对该文件的类型,选择调用 “cp_file” 或者 “cp_dir”。实际上,linux下的文件类型有7种,我们应该对于每种类型,都实现一个拷贝函数,但此处仅仅是一个简洁版本,所以只处理目录文件和普通文件两种。

首先介绍读目录相关的接口:

DIR *opendir(const char *name);
功能:打开一个目录;
参数:目录的路径;
返回值:若打开成功返回一个指向文件流的指针,用户可以通过该指针访问目录下的文件,若打开失败,则返回 NULL;
说明:一般情况下打开失败的原因包括:
 1) 路径为空或者路径非法;
 2) 进程可打开的文件描述符数量达到上限;
 3) 系统可打开的文件描述符达到上限.
struct dirent *readdir(DIR *dirp);
功能:读取目录的内容; 
参数:指向文件流的指针;
返回值:若 执行成功,返回指向文件文件结构体(结构体内容如下)的指针,若读到目录结尾,则返回 NULL。   

下面是拷贝目录的代码:

int cp_dir(const char *src, const char *dst, mode_t mode){
    LOG("copy dir: '%s' to '%s'\n", src, dst);
    if(src == NULL || dst == NULL){
        return -1;
    }
    (void)mode;
    DIR *fd_dst = opendir(src);
    struct dirent *fd_read = NULL;
    while((fd_read=readdir(fd_dst)) != NULL) {
        if(fd_read->d_name[0] == '.'){
            continue;
        }

        char *path_src = (char*)malloc(strlen(src)+strlen(fd_read->d_name)+1);
        sprintf(path_src, "%s/%s", src, fd_read->d_name);
        char *path_dst = (char*)malloc(strlen(dst)+strlen(fd_read->d_name)+1);
        sprintf(path_dst, "%s/%s", dst, fd_read->d_name);
        struct stat sbuf;
        memset(&sbuf, 0x00, sizeof(sbuf));
        if(stat(path_src, &sbuf) == -1){
            LOG("file stat error!\n");
        }
        if(S_ISREG(sbuf.st_mode)){//源是文件
            cp_file(path_src, path_dst, sbuf.st_mode);
        }else if(S_ISDIR(sbuf.st_mode)){//源是目录
            mkdir(path_dst, sbuf.st_mode);
            cp_dir(path_src, path_dst, sbuf.st_mode);
        }
        free(path_src);
        free(path_dst);
    }
    return 0;
}

文件的拷贝实际上是将源文件内容读出来,然后写到目标文件中,具体代码如下:

int cp_file(const char *src, const char *dst, mode_t mode){
    LOG("copy file: '%s' to '%s'\n", src, dst);
    if(src == NULL || dst == NULL){
        return -1;
    }
    int fd_src = open(src, O_RDONLY);
    if(fd_src == -1){
        LOG("open src file error: %s\n", src);
    }
    int fd_dst = open(dst, O_WRONLY | O_CREAT, mode);
    if(fd_dst == -1){
        LOG("open dst file error: %s\n", dst);
    }
    char buff[1024] = {0};
    ssize_t size = 0;
    int totol_size = 0;
    while((size = read(fd_src, buff, 1023)) > 0){
        totol_size += size;
        buff[size] = '\0';
        write(fd_dst, buff, size);
        memset(buff, 0x00, 1024);
    }
    return totol_size;
}

完整代码

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <libgen.h>

#ifdef DEBUG
#define LOG(format, ...)                                                                   \
do{                                                                                         \
    time_t t = time(0);                                                                     \
    struct tm ptm;                                                                          \
    memset(&ptm, 0, sizeof(ptm));                                                           \
    localtime_r(&t, &ptm);                                                                  \
    printf("[LOG][%4d-%02d-%02d %02d:%02d:%02d][%s:%s:%d]:\033[31m " format "\033[0m", \
            ptm.tm_year + 1900, ptm.tm_mon + 1, ptm.tm_mday, ptm.tm_hour,                   \
            ptm.tm_min, ptm.tm_sec, __FILE__, __FUNCTION__ , __LINE__, ##__VA_ARGS__);      \
    fflush(stdout);                                                                         \
}while(0)
#else
#define LOG(format, ...)                                                                   
#endif

int cp_file(const char *src, const char *dst, mode_t mode);
int cp_dir(const char *src, const char *dst, mode_t mdoe);


int main(int argc, char *argv[]){
    if(argc != 3){
        usage(argv[0]);
        exit(0);
    }

    if(strcmp(argv[1], argv[2]) == 0){
        LOG("cp: cannot copy a directory, '%s', into itself, '%s/%s'\n", argv[1], argv[1], argv[2]);
        exit(0);
    }


    struct stat sbuf;
    memset(&sbuf, 0x00, sizeof(sbuf));
    if(stat(argv[1], &sbuf) == -1){
        LOG("file stat error!\n");
    }

    if(S_ISREG(sbuf.st_mode)){//源是文件
        LOG("src is file\n");
        struct stat dbuf;
        memset(&dbuf, 0x00, sizeof(dbuf));
        if(stat(argv[2], &dbuf) == -1){//目标不存在
            LOG("dst not exist\n");
            cp_file(argv[1], argv[2], sbuf.st_mode);
        }else if(S_ISREG(dbuf.st_mode)){//目标是文件
            LOG("dst is file\n");
            char c = 'n';
            printf("cover dst file? [Y/n]");
            scanf(" %c", &c);
            if(c == 'y' || c == 'Y'){
                truncate(argv[2], 0);
                cp_file(argv[1], argv[2], sbuf.st_mode);
            }
        }else if(S_ISDIR(dbuf.st_mode)){//目标是目录
            LOG("dst is dir\n");
            char temp[1024] = {0};
            sprintf(temp, "%s/%s", argv[2], argv[1]);
            cp_file(argv[1], temp, sbuf.st_mode);
        }
    }else if(S_ISDIR(sbuf.st_mode)){//源是目录
        LOG("src is dir\n");
        struct stat dbuf;
        memset(&dbuf, 0x00, sizeof(dbuf));
        if(stat(argv[2], &dbuf) == -1){//目标不存在
            LOG("dst not exist\n");
            mkdir(argv[2], sbuf.st_mode);
            cp_dir(argv[1], argv[2], sbuf.st_mode);
        }else if(S_ISDIR(dbuf.st_mode)){//目标是目录
            LOG("dst is dir\n");
            char temp[1024] = {0};
            sprintf(temp, "%s/%s", argv[2], argv[1]);
            mkdir(temp,sbuf.st_mode);
            cp_dir(argv[1], temp, sbuf.st_mode);
        }else{//非法操作
            LOG("mycp: cannot overwrite non-directory [%s] with directory [%s]\n", argv[2], argv[2]);
        }
    }

    return 0;
}
总结

我们实现的 cp 命令不到 200 行,而 linux 系统中的实现有 2000 行左右,所以,我们的版本还有许多改进的地方,比如:增加拷贝各种类型文件的功能,采用其它内核读取文件的接口以提高时效性。

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值