c++实现文件复制并修改相应属性
问题描述
完成一个目录复制命令mycp,包括目录下的文件和子目录, 运行结果如下:
beta@bugs.com [~/]# ls –la sem
total 56
drwxr-xr-x 3 beta beta 4096 Dec 19 02:53 ./
drwxr-xr-x 8 beta beta 4096 Nov 27 08:49 ../
-rw-r--r-- 1 beta beta 128 Nov 27 09:31 Makefile
-rwxr-xr-x 1 beta beta 5705 Nov 27 08:50 consumer*
-rw-r--r-- 1 beta beta 349 Nov 27 09:30 consumer.c
drwxr-xr-x 2 beta beta 4096 Dec 19 02:53 subdir/
beta@bugs.com [~/]# mycp sem target
beta@bugs.com [~/]# ls –la target
total 56
drwxr-xr-x 3 beta beta 4096 Dec 19 02:53 ./
drwxr-xr-x 8 beta beta 4096 Nov 27 08:49 ../
-rw-r--r-- 1 beta beta 128 Nov 27 09:31 Makefile
-rwxr-xr-x 1 beta beta 5705 Nov 27 08:50 consumer*
-rw-r--r-- 1 beta beta 349 Nov 27 09:30 consumer.c
drwxr-xr-x 2 beta beta 4096 Dec 19 02:53 subdir/
思路
这道题目主要涉及文件读写操作和属性修改。需要支持文件夹复制、文件复制,在Linux下还要支持软链接的复制。
思路如下:
- 获取待复制目录的绝对路径
- 根据绝对路径进行dfs或者bfs搜索所有子目录项
- 判断子目录是属于什么类型:文件夹、普通文件、软链接
- 分别对三种(Windows下只有文件夹和文件)进行复制操作
- 修改目标文件属性与源文件保持一致
使用到的函数主要有:
Linux
判断文件类型
-
int lstat(const char *pathname, struct stat *statbuf);
const char *pathname
需要判断的文件的路径struct stat *statbuf
用于保存文件属性return int
0成功,-1失败
-
判断
文件类型 说明 判断函数 例子 普通文件 一般意义上的文件 S_ISREG() hello.c 目录文件 可包含其他文件或者目录 S_ISDIR() /etc/ 字符设备文件 以无缓冲方式,提供对于设备的可变长度访问 S_ISCHR() /dev/tty 块设备文件 以带缓冲方式,提供对于设备的固定长度访问 S_ISBLK() /dev/sda 符号链接文件 指向另一个文件 S_ISLNK() /dev/cdrom 命名管道文件 用于进程间通信 S_ISFIFO() /dev/inictl 网络套接字文件 用于进程间的网络通信 S_ISSOCK() /dev/log 值得注意的是,需要先判断是不是符号链接文件。对符号链接文件进行
S_ISREG()
判断时会出现将其判断为普通文件的情况。可能是由于他判断的是链接文件所指向的文件的类型。因此需要先判断是不是链接文件。
遍历文件目录
-
DIR *opendir(const char *name);
const char *name
待打开的目录路径return DIR
返回目录数据结构
-
struct dirent *readdir(DIR *dirp);
-
``````DIR *dirp``` 待读取的目录
-
return struct dirent*
返回顺序读取的该目录下的目录项注意,第一个目录项是
.
, 第二个目录项是..
-
复制文件
int open(const char *pathname, int flags, mode_t mode);
const char* pathname
待打开的文件路径int flags
O_RDONLY, O_WRONLY, or O_RDWR
mode_t mode
return int
返回值是打开的文件描述符
int creat(const char *pathname, mode_t mode);
const char* pathname
待创建的文件名(可以包含路径)mode_t mode
创建属性return int
待创建文件描述符
ssize_t read(int fd, void *buf, size_t count);
int fd
待读取的文件的描述符void* buf
读取的内容存放缓冲区。尽管这里是void*
,buf
在创建的时候应该是```char*``````- ``````size_t count
要读取的内容的大小。如果大于
fd```中的数目,则读取相应大小内容 return ssize_t
返回实际读取的内容的数目。失败返回-1
ssize_t write(int fd, const void *buf, size_t count);
int fd
要写入的文件的描述符const void *buf
要写入的内容。size_t count
要写入的内容大小return ssize_t
成功返回实际写入的数目。失败返回-1
复制文件夹
复制文件夹就是创建同名文件夹
int mkdir(const char *pathname, mode_t mode);
const char* pathname
待创建的目录项的地址mode_t mode
创建目录项的属性return int
如果成功返回为0,否则返回-1
复制软链接
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
const cha* pathname
待读取软链接的路径char* buf
软链接中的内容保存到buf
中size_t bufsiz
buf
缓冲区的大小return ssize_t
返回值是软链接指向路径的长度
int symlink(const char *target, const char *linkpath);
const char* target
待创建的软链接的路径const char* linkpath
待创建的软链接所指向的路径return int
成功返回0,否则返回-1
获取属性
-
int lstat(const char *pathname, struct stat *statbuf);
const char *pathname
需要提取属性的文件或者文件夹的路径struct stat *statbuf
获取到的属性存储缓冲区return int
成功返回0,否则-1
-
int chmod(const char *pathname, mode_t mode);
const char *pathname
待修改属性的文件的路径mode_t mode
将待修改文件修改改为该属性return int
若成功返回0,否则返回-1
-
int chown(const char *pathname, uid_t owner, gid_t group);
const char* pathname
待更改的目录路径uid_t owner
如果是-1则不改变属性gid_t group
如果是-1则不改变属性
-
int lutimes(const char *filename, const struct timeval tv[2]);
这个命令用于修改目标文件的
access_time
modify_time
。lutimes
可以修改软链接的属性。const char *filename
待修改的文件路径const struct timeval tv[2]
tv[0]
是access_time
,tv[1]
是modify_time
return int
如果成功返回0,否则返回-1
Windows
这里主要列出所用到的函数,有需要的话可以详细去MSDN查相关的函数说明:
FindFirstFile
FindNextFile
CreateFile
GetFileSize
ReadFile
WriteFile
_wmkdir
GetFileAttributes
SetFileAttributes
GetFileTime
SetFileTime
源代码实现
Linux
#include <iostream>
#include <string>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
#include <time.h>
#include <sys/time.h>
//文件夹复制采用dfs或者bfs进行搜索是等价的
void searchDfs(char *src_path, char *dest_path);
//复制文件
void copyFile(const char *src_file, const char *dst_file);
//复制文件夹
void copyDir(const char *src_dir, const char *dst_dir);
//复制软链接
void copySln(const char *src_file, const char *dst_file);
//修改文件属性与源文件保持一致
void changeAttr(const char *src, const char *dst);
//在src路径后面加上cat路径
void changePathname(char *src, char *cat)
{
//在src路径后面加上cat路径
strcat(src, (char *)"/");
strcat(src, cat);
}
int main(int argc, char const *argv[])
{
//异常处理
if (argc < 3)
{
printf("No file or directory specified\n");
exit(-1);
}
if (argc > 3)
{
printf("Too many arguments\n");
exit(-1);
}
char src_path[1024], dest_path[1024];
char *current_dir = getcwd(NULL, 0); //获得当前路径
//判断需要复制的是文件还是文件夹
struct stat state_of_entry;
lstat(argv[1], &state_of_entry);
if (S_ISDIR(state_of_entry.st_mode)) //如果要复制的是文件夹
{
if (chdir(argv[1])) //目录错误
{
perror("chdir");
exit(-1);
}
strcpy(src_path, getcwd(NULL, 0)); //获取源文件夹绝对路径
chdir(current_dir);
//判断第二个参数是不是目标路径
lstat(argv[2], &state_of_entry);
if (S_ISDIR(state_of_entry.st_mode)) //目录
{
if (chdir(argv[2])) //目录错误
{
perror("chdir");
exit(-1);
}
strcpy(dest_path, getcwd(NULL, 0)); //获取目标文件夹绝对路径
chdir(current_dir);
chdir(dest_path);
chdir(src_path); //切换到要复制的文件夹
searchDfs(src_path, dest_path);
}
//如果目标路径错误
else
{
printf("error. No destination directory.\n");
exit(-1);
}
}
else //文件直接复制
{
char dest_path[1024];
lstat(argv[2], &state_of_entry);
if (S_ISDIR(state_of_entry.st_mode)) //目录
{
strcpy(dest_path, getcwd(NULL, 0)); //获取目标文件夹绝对路径
}
else
{
strcpy(dest_path, "./");
strcat(dest_path, argv[2]);
}
copyFile(argv[1], argv[2]);
}
return 0;
}
void searchDfs(char *src_path, char *dest_path)
{
DIR *src_dir = opendir(src_path);
DIR *dest_dir = opendir(dest_path);
struct dirent *entry = NULL;
struct stat state_of_entry;
while ((entry = readdir(src_dir)) != NULL)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
lstat(entry->d_name, &state_of_entry);
if (S_ISLNK(state_of_entry.st_mode)) //符号链接
{
char src_file[1024];
char dest_file[1024];
strcpy(src_file, src_path);
//获取源文件绝对路径
changePathname(src_file, entry->d_name);
strcpy(dest_file, dest_path);
//获取目标文件绝对路径
changePathname(dest_file, entry->d_name);
//复制
copySln(src_file, dest_file);
}
else if (S_ISREG(state_of_entry.st_mode)) //普通文件
{
char src_file[1024];
char dest_file[1024];
strcpy(src_file, src_path);
changePathname(src_file, entry->d_name);
strcpy(dest_file, dest_path);
changePathname(dest_file, entry->d_name);
copyFile(src_file, dest_file);
}
else if (S_ISDIR(state_of_entry.st_mode)) //目录
{
char src[1024];
char dest[1024];
strcpy(src, src_path);
changePathname(src, entry->d_name);
strcpy(dest, dest_path);
changePathname(dest, entry->d_name);
copyDir(src, dest);
searchDfs(src, dest);
}
}
//保持目标文件夹与源文件夹属性相同
changeAttr(src_path, dest_path);
}
void copyFile(const char *src_file, const char *dest_file)
{
int src_fd = open(src_file, O_RDONLY);
int dest_fd = creat(dest_file, O_WRONLY);
unsigned char buf[1024];
while (read(src_fd, buf, sizeof(buf)) > 0)
{
write(dest_fd, buf, sizeof(buf));
}
changeAttr(src_file, dest_file);
close(src_fd);
close(dest_fd);
}
void copyDir(const char *src_dir, const char *dst_dir)
{
mkdir(dst_dir, 0777);
changeAttr(src_dir, dst_dir);
}
void copySln(const char *src_file, const char *dst_file)
{
//软链接所指向的文件路径
char slink_path[1024];
memset(slink_path, 0, sizeof(slink_path));
int len = 0;
if ((len = readlink(src_file, slink_path, sizeof(slink_path))) > 0)
{
char src_dir[1024];
strcpy(src_dir, src_file);
//获取源文件的绝对路径,不含文件名
for (int i = strlen(src_dir); i > -1; i--)
{
if (src_dir[i] == '/')
{
src_dir[i + 1] = 0;
break;
}
}
if (slink_path[0] != '/') //一定是相对路径,如果首字符是'/'则是从根目录出发的绝对路径
strcat(src_dir, slink_path); //将相对软链接的路径添加到软链接文件路径的后面
else
strcpy(src_dir, slink_path); //绝对路径拷贝过去即可
//保存软链接所指向的文件的名字
char file_name[1024];
//提取所指向的文件的目录,由于可能是相对路径,顾切换到对应的文件夹下获取绝对路径
for (int i = strlen(src_dir); i > -1; i--)
{
if (src_dir[i] == '/')
{
strcpy(file_name, src_dir + i);//将软链接所指向的文件名保存在file_name中
src_dir[i + 1] = 0;
break;
}
}
chdir(src_dir);//切换到软链接所指向的文件的路径
strcpy(src_dir, getcwd(NULL, 0)); //获取软链接所指向文件的绝对路径
strcat(src_dir, file_name);//添加软链接所指向的文件名即组成了绝对路径
//创建软链接
if (symlink(src_dir, dst_file) == -1)
{
perror("symlink");
}
//修改目标软链接属性使其与源软链接属性一致
changeAttr(src_file, dst_file);
}
else
{
printf("%s: 软链接错误\n",src_file);
}
}
void changeAttr(const char *src, const char *dst)
{
struct stat attr_of_src;
lstat(src, &attr_of_src);
//修改文件属性
chmod(dst, attr_of_src.st_mode);
//修改文件用户组
chown(dst, attr_of_src.st_uid, attr_of_src.st_gid);
//修改文件访问、修改时间
if (S_ISLNK(attr_of_src.st_mode))
{
struct timeval time_buf[2];
time_buf[0].tv_sec = attr_of_src.st_atim.tv_sec;
time_buf[0].tv_usec = attr_of_src.st_atim.tv_nsec / 1000;
time_buf[1].tv_sec = attr_of_src.st_mtim.tv_sec;
time_buf[1].tv_usec = attr_of_src.st_mtim.tv_nsec / 1000;
if (lutimes(dst, time_buf) == -1)
{
printf("%s\n", dst);
perror("lutimes");
}
}
else
{
struct utimbuf tbuf;
tbuf.actime = attr_of_src.st_atime;
tbuf.modtime = attr_of_src.st_mtime;
utime(dst, &tbuf);
}
}
Windows
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <cstring>
#define BUF_SIZE 1024
using namespace std;
//文件夹复制采用dfs或者bfs进行搜索是等价的
void searchDfs(wchar_t* src_path, wchar_t* dest_path);
//复制文件
void copyFile(const wchar_t* src_file, const wchar_t* dst_file);
//复制文件夹
void copyDir(const wchar_t* src_dir, const wchar_t* dst_dir);
//修改文件属性与源文件保持一致
void changeAttr(const wchar_t* src_name, const wchar_t* dst_name, HANDLE h_src, HANDLE h_dst);
int wmain(int argc, wchar_t* argv[])
{
setlocale(LC_ALL, "");//设置wchar支持中文
wchar_t* src_path = new wchar_t[BUF_SIZE];
wchar_t* dst_path = new wchar_t[BUF_SIZE];
ZeroMemory(src_path, BUF_SIZE);
ZeroMemory(dst_path, BUF_SIZE);
wcscpy(src_path, argv[1]);//获取源文件夹路径
wcscat(src_path, L"\\*");//末尾添加"\\*"表明要对其进行遍历
wcscpy(dst_path, argv[2]);//获取目标文件夹路径
searchDfs(src_path, dst_path);//对源文件夹进行搜索遍历复制
delete[] src_path;
delete[] dst_path;
return 0;
}
void searchDfs(wchar_t* src_path, wchar_t* dst_path)
{
//保存文件夹下的目录信息
WIN32_FIND_DATA find_data;
//第一个文件的handle
HANDLE h_find = FindFirstFile(src_path, &find_data);
if (h_find == INVALID_HANDLE_VALUE)
{
cout << "Fail to find first file" << endl;
return;
}
copyDir(src_path, dst_path);//目标文件夹不存在时先创建
//遍历该文件夹下的所有文件和文件夹
do
{
//wcout << find_data.cFileName << endl;
if (wcscmp(find_data.cFileName, L".") == 0 || wcscmp(find_data.cFileName, L"..") == 0)
{
continue;
}
//该项是目录
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
wchar_t n_src_path[BUF_SIZE];
wcscpy(n_src_path, src_path);
n_src_path[wcslen(n_src_path) - 1] = '\0';//删除最后一位的'*'
wcscat(n_src_path, find_data.cFileName);//添加下层文件夹名称以获取下级目录
wcscat(n_src_path, L"\\*");
wchar_t n_dst_path[BUF_SIZE];
wcscpy(n_dst_path, dst_path);
wcscat(n_dst_path, L"\\");
wcscat(n_dst_path, find_data.cFileName);//要复制到的目标文件夹路径
copyDir(n_src_path, n_dst_path);//复制文件夹
searchDfs(n_src_path, n_dst_path);//向下搜索
}
else//该项是文件
{
//获得该项的绝对路径
wchar_t n_src_path[BUF_SIZE];
wcscpy(n_src_path, src_path);
n_src_path[wcslen(n_src_path) - 1] = '\0';//删除最后一位的'*'
wcscat(n_src_path, find_data.cFileName);//添加文件名称以获取绝对路径
//要复制到的文件的绝对路径
wchar_t n_dst_path[BUF_SIZE];
wcscpy(n_dst_path, dst_path);
wcscat(n_dst_path, L"\\");
wcscat(n_dst_path, find_data.cFileName);
copyFile(n_src_path, n_dst_path);
}
} while (FindNextFile(h_find, &find_data));
//目标文件夹已存在,这里实际上只是修改目的权限与源权限一致
copyDir(src_path, dst_path);
return;
}
//复制文件
void copyFile(const wchar_t* src_file, const wchar_t* dst_file)
{
//打开源文件
HANDLE h_src = ::CreateFile(
src_file,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
//创建目标文件
HANDLE h_dst = ::CreateFile(
dst_file,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (h_src == INVALID_HANDLE_VALUE || h_dst == INVALID_HANDLE_VALUE)
{
printf("Open file error!\n");
CloseHandle(h_src);
CloseHandle(h_dst);
exit(-1);
}
DWORD all_bytes = GetFileSize(h_src, NULL);
char* buffer = new char[all_bytes + 1];
ZeroMemory(buffer, all_bytes + 1);
//复制文件
DWORD bytes = 0;
ReadFile(h_src, buffer, all_bytes, &bytes, NULL);
WriteFile(h_dst, buffer, all_bytes, &bytes, NULL);
//修改文件属性
changeAttr(src_file, dst_file, h_src, h_dst);
CloseHandle(h_src);
CloseHandle(h_dst);
delete[] buffer;
return;
}
//复制文件夹
void copyDir(const wchar_t* src_dir_name, const wchar_t* dst_dir)
{
//获得源目录路径
wchar_t* src_dir = new wchar_t[BUF_SIZE];
wcscpy(src_dir, src_dir_name);
src_dir[wcslen(src_dir) - 2] = '\0';
//创建文件夹
_wmkdir(dst_dir);
//打开源文件夹
HANDLE h_src = CreateFile(
src_dir,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if (h_src == INVALID_HANDLE_VALUE)
{
printf("Open src directory error!\n");
CloseHandle(h_src);
exit(-1);
}
//打开目标文件夹
HANDLE h_dst = CreateFile(
dst_dir,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if (h_dst == INVALID_HANDLE_VALUE)
{
printf("Open dst directory error!\n");
CloseHandle(h_dst);
exit(-1);
}
//修改目标文件夹属性与源文件夹属性一致
changeAttr(src_dir, dst_dir, h_src, h_dst);
CloseHandle(h_src);
CloseHandle(h_dst);
return;
}
//修改文件属性与源文件保持一致
void changeAttr(const wchar_t* src_name, const wchar_t* dst_name, HANDLE h_src, HANDLE h_dst)
{
//修改文件属性
DWORD attr = GetFileAttributes(src_name);
SetFileAttributes(dst_name, attr);
//修改访问、修改时间
FILETIME t_create, t_access, t_write;
SYSTEMTIME syst_create, syst_access, syst_write;
GetFileTime(h_src, &t_create, &t_access, &t_write);
SetFileTime(h_dst, &t_create, &t_access, &t_write);
}