程序名 | 功能 |
---|---|
touch | 创建文件 |
rm | 删除文件 |
cp | 复制文件 |
mv | 移动文件 |
cat | 查看文件内容 |
ls | 列举当前目录下文件 |
那么说到文件系统,实际上本节讨论的反而不是文件系统本身的问题。因为文件系统本身是个很复杂的东西,从Windows诞生到现在已经经历过好几种文件系统 如FAT16、FAT32、到现在的NTFS等等。撇开windows不谈,linux平台下的文件系统更是多的去了,什么ext系列reiserfs、xfs、jfx等等,真心很多。
那么博主这里讨论的文件系统主要是讨论windows下围绕文件系统的的一系列操作。自然还包括一系列的文件系统基本概念。
学过C++的可能比较好理解,学过C的可以把文件对象当成是标准库中操作文件的FILE结构体。
当我们开始操作一个文件的时候,这个文件的打开关闭都由程序控制,而其中的内容是可以动态增减的,这样的情况类似水流一般,我们把这样的文件状态称作文件流。
当我们打开文件的时候,系统分配的一个句柄标识。
博主看来,这个通常是文件读写操作时,存储相对于文件起始位置的一个指针。
CreateFile | 创建、打开文件 |
ReadFile | 读取文件内容 |
WriteFile | 写入文件内容 |
SetFilePointer | 移动文件指针 |
SetEndOfFile | 设置文件结尾标志 |
CopyFile | 文件拷贝 |
DeleteFile | 文件删除 |
MoveFile | 文件移动 |
CreateDirectory | 创建一个目录 |
RemoveDirectory | 删除一个目录 |
GetCurrentDirectory | 获取当前程序所在目录 |
SetCurrentDirectory | 设置当前程序所在目录 |
FindFirstFile | 查找指定目录下的第一个文件 |
FindNextFile | 查找下一个文件 |
LockFile | 文件锁定 |
UnlockFile | 文件解锁 |
GetFileType | 获取文件类型 |
GetFileSize | 获取文件的大小 |
GetFileAttributes | 获取文件属性 |
SetFileAttributes | 设置文件属性 |
GetFileTime | 获取文件时间 |
GetFileInformationByHandle | 获取文件信息 |
GetFullPathName | 获取文件的完整路径 |
GetModuleFileName | 获取当前模块全路径 |
文档详见:MSDN 文件操作 详细文档
CreateFileMapping | 创建文件的映射对象 |
MapViewOfFile | 创建视图,将创建的文件映射对象到当前进程的地址空间中 |
FlushViewOfFile | 将视图中数据都写入磁盘,对视图的操作都会反映到磁盘上的文件中 |
OpenFileMapping | 打开已存在的文件映射 |
UnmapViewOfFile | 取消文件映射 |
QueryDosDevice | 查询 MS-DOS 设备的名称 |
详细参见:MSDN 内存操作 详细文档
各位可以跟着博主的步骤慢慢的开始学着使用windows API。那么我们先来做些小应用,准备实现一个功能,即创建一个空的文件。
那么我们先去翻查 API 的列表,发现了 CreateFile 这个函数,如果读者以后再编程的时候没有找到合适的 API 可以直接上 微软的官方网站 MSDN 上面找完整的 API 目录。
CreateFile 的 MSDN 官方文档可以找到:CreateFile function
最后看到函数原型:
1
2
3
4
5
6
7
8
9
|
HANDLE
WINAPI CreateFile(
_In_
LPCTSTR
lpFileName,
// 文件名
_In_
DWORD
dwDesiredAccess,
// 访问方式
_In_
DWORD
dwShareMode,
// 共享模式
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// 安全属性
_In_
DWORD
dwCreationDisposition,
// 创建凡是
_In_
DWORD
dwFlagsAndAttributes,
// 文件属性
_In_opt_
HANDLE
hTemplateFile
// 模板文件句柄
);
|
那么首先稍微了解一下,_In_和 _In_opt_,如同第一节中提到过的 far 和 near 这两个也是用来做标识的宏,其中_In_表示该参数是一个输入参数,即数值直接传入;_Out_则是指输出参数,一般传入指针,函数执行中会通过指针对该实参赋值;而_opt_选项是表示该参数是可选的,即,可以设置为空。
lpFileName 输入参数
操作对象文件的相对路径或绝对路径。需要注意的就是文件名中的特殊字符,比如空格需要转义等等,还有就是文件名的长度不要超过系统限制(ANSI版 请使用 MAX_PATH 宏)。
dwDesiredAccess 输入参数
指明对文件对象的操作存取方式,最常用的是
常量 | 通用意义 (Generic meaning) |
---|---|
GENERIC_READ | 读取权限 |
GENERIC_WRITE | 写入权限 |
GENERIC_EXECUTE | 执行权限 |
GENERIC_ALL | 所有权限 |
你可以设置文件打开是读(GENERIC_READ)还是写(GENERIC_WRITE)亦或是读写 (GENERIC_READ | GENERIC_WRITE)。
dwShareMode 输入参数
共享模式。指明与其他进程是否共享该文件,可以是共享读(FILE_SHARE DELETE)、共享写(FILE_SHARE_WRITE)、共享删除(FILE_SHARE.READ),如果要指明多个属性,使用“位与~‘I”运算。如果指明上述参数,其他进程就可以对文件进行相关操作。如果本进程需要独占本文件,则将本参数设置为 0。
值 | 意义 |
---|---|
0 | 阻止其他进程对当前文件或设备进行删除或读写 |
FILE_SHARE_DELETE | 允许其他进程通过 “删除” 权限打开。 因此其他进程无法通过 “删除” 权限打开当前文件或设备。 如果没有设置该项,但是该文件或设备已经以 “删除” 的权限打开,那么 这个方法会调用失败。 注: “删除” 权限 允许 “删除” 和重命名操作。 |
FILE_SHARE_READ | 允许其他进程通过 “读取” 权限打开。 因此其他进程无法通过 “读取” 权限打开当前文件或设备。 如果没有设置该项,但是该文件或设备已经以 “读取” 的权限打开,那么 这个方法会调用失败。 |
FILE_SHARE_WRITE | 允许其他进程通过 “写入” 权限打开。 因此其他进程无法通过“写入”权限打开当前文件或设备。 如果没有设置该项,但是该文件或设备已经以 “写入” 的权限打开,或者已经有一个文件以执行的权限打开,那么这个方法会调用失败。 |
lpSecurityAttributes 输出参数(可选)
指向 SECURITY_ATTRIBUTES结构的指针,表示本文件句柄的安全属性,能影响其是否可被子进程继承等操作。如果设定为NULL,则子进程不可继承本句柄。SECURITY ATTRIBUTES 结构不常用,对此数据结构的设置,涉及Windows系统中对权限管理的原理,在本节中不作详细说明。
dwCreationDisposition 输入参数
Value | Meaning |
---|---|
CREATE_ALWAYS | 总是创建一个新文件。 如果指定的文件存在并且是可写的,这个方法会清除内容,重写一遍。这样的情况下,即使成功了 last-error(类似于C中的错误流)也会被设置成 ERROR_ALREADY_EXISTS (183)。 如果指定的文件不存在并且文件名是一个有效路径,一个新的文件会被创建, 函数调用成功, 并且 last-error 会被设置成 0。 更多的信息, see the Remarks section of this topic。 |
CREATE_NEW | 仅当该文件不存在时创建一个新文件。 如果指定的文件存在,这个方法会失败 并且 last-error 会被设置成 ERROR_FILE_EXISTS (80)。 如果指定的文件不存在并且文件名是一个可写目录下的有效路径,一个新的文件会被创建。 |
OPEN_ALWAYS | 总是打开这个文件。 如果指定的文件 存在, 函数调用成功 并且 last-error 会被设置成 ERROR_ALREADY_EXISTS (183)。 如果指定的文件不存在,并且文件名是一个可写目录下的有效路径,该函数会创建一个文件并且 last-error 会被设置成 0。 |
OPEN_EXISTING | 仅当该文件或设备存在时,打开它。 如果指定的文件或设备不存在,这个方法会失败 并且 last-error 会被设置成 ERROR_FILE_NOT_FOUND (2)。 |
TRUNCATE_EXISTING | 打开一个文件并且清空(truncates)它(仅当该文件存在)。 如果指定的文件 不存在,这个方法会失败 并且 last-error 会被设置成 ERROR_FILE_NOT_FOUND (2)。 这个过程的调用必须设置 dwDesiredAccess 参数为 GENERIC_WRITE 。 |
dwFlagsAndAttributes 输入参数
文件属性和文件标志。一般情况下文件属性较常用,而操作标志不常用,可以使用“1”运算符指定多个属性和标志。
属性 | 意义 |
---|---|
FILE_ATTRIBUTE_ARCHIVE | 存档文件 应用程序 使用这个属性来 标记文件的 备份或删除 |
FILE_ATTRIBUTE_ENCRYPTED | 加密文件, 这意味着文件中的所有数据都是加密的. 对于一个目录而言, 这意味着其包含的文件以及子目录都默认是加密的。 更多的信息,请搜索 File Encryption。 如果 FILE_ATTRIBUTE_SYSTEM 也同时被设置,那么该属性不会起任何作用。 PS: 该属性在 windows 的家庭版、家庭高级版、Starter或者ARM版本上不支持 |
FILE_ATTRIBUTE_HIDDEN | 隐藏文件。 请不要将其放在普通的目录下. |
FILE_ATTRIBUTE_NORMAL | 未设置其他属性。只能单独使用(不能 “|” ) |
FILE_ATTRIBUTE_OFFLINE | 离线存储文件该属性表明文件数据被物理的移动到离线存储器(offline storage)。 该属性通常用于操作远程文件。应用程序请不要随意地设置该属性. |
FILE_ATTRIBUTE_READONLY | 文件只读 |
FILE_ATTRIBUTE_SYSTEM | 系统文件。 |
FILE_ATTRIBUTE_TEMPORARY | 临时文件 更多的信息, see the Caching Behavior section of this topic. |
hTemplateFile 输入参数(可选)
当存取权限包括 GENERIC_WRITE 时,可以设置为一个模板
文件的句柄。一般情况下,可设置为 NULL,表示不使用模板文件。
如果成功则返回HANDLE数据类型的文件的句柄,如果失败,则返回 NVALID_HANDLE_VALUE,想要查看具体的错误情况请使用 GetLastError 函数。
是否被上述详细的列表给看花眼了?好吧,各位淡定,这个只是给大家举个例子,表示每个函数的详细信息都是可以查得到的(如果搜不到中文的,就老老实实的上MSDN吧)
那么我们继续准备实现一个功能,即创建一个空的文件。只需要简单的调用该函数即可,那么大家打开各自的IDE准备开始编写,注意我们要建的项目类型是空项目,就是刚出来什么都没有的那种,项目名称最好是叫 touch (因为最后生成的exe名称就是你的项目名称)并且注意你的项目所保存的路径,因为等会程序写好了是要切换到那个目录的。
空项目建好之后,在源代码文件夹处右键新建一个 .c 文件(不行先用.cpp文件也是可以的):
main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#include
#include
void
help();
int
main(
int
argc,
char
const
*argv[])
{
int
i;
// 参数数目为1 即用户只输入程序名,程序仅收到一个参数
if
(argc == 1)
{
help();
return
0;
}
// 遍历指针数组所指向的参数
for
(i = 1; i < argc; i++)
{
CreateFile(
argv[i],
// 文件名
GENERIC_WRITE,
// 写入权限
0,
// 阻止其他进程访问
NULL,
// 子进程不可继承本句柄
CREATE_NEW,
// 仅不存在时创建新文件
FILE_ATTRIBUTE_NORMAL,
// 普通文件
NULL);
// 不适用模板文件
}
return
0;
}
void
help()
{
printf
(
"创建新文件:"
);
printf
(
"touch <文件名1> <文件名2> ..."
);
}
|
编写好了之后,注意只需要生成即可(快捷键F7,不用F5打开运行),生成之后就可以在debug目录下找到exe文件。这个时候我们打开cmd,切换到项目的Debug目录下(cmd基础操作见《CMD扫盲教程》)调用我们刚刚写的程序:
可以看到调用程序很简单的就建立了三个txt文件,注意参数也是可以加路径的哦,例如 touch F:\1.txt也是可以的。
那么,接下来我们就可以在某个地方建个专用文件夹来存放自己写的多个函数,例如 F:/mytools 目录,然后把刚刚的touch.exe复制到这个目录下,接着将 F:/mytools 添加到环境变量(cmd 设置PATH环境变量)。 以后 cmd 中就可以随时随地的用自己写的这些程序了。(博主是准备带大家写一系列的工具集)
大家可以使用各种搜索引擎去搜索,百度不到就用谷歌。当然官方文档还是要去MSDN的,这边直接上MSDN就可以搜到了,注意MSDN的搜索关键字最好是 方法名+function,例如 DeleteFile Function : http://msdn.microsoft.com/en-us/library/windows/desktop/aa363915(v=vs.85).aspx
函数原型:
1
2
3
|
BOOL
WINAPI DeleteFile(
_In_
LPCTSTR
lpFileName
// 删除文件名
);
|
DeleteFile的功能是删除一个已存在的文件文件。
lpFileName 输入参数
所要删除的文件的相对路径(如"\1.txt"相对于当前的路径) 或绝对路径 (如"F:\1.txt")。
返回BOOL值,那么第一节的时候我们也看到过了 BOOL 其实就是int类型的同义字。如果该函数执行成功的话,会返回一个非零的数,如果失败的话,会返回零。想知道错误信息的话,就请使用 GetLastError 函数。
这个函数的参数很少,但是大家也不要小觑。那么我们现在再来调用这个API实现个小功能。那就是删除文件。
还是跟上面一样,空项目,项目名称 “rm” (最后生成exe的名称)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
#include
#include
#define FILE_DEL_MAX 10
void
help();
struct
_del_attr {
int
Force_del;
// 强制删除选项
} ;
struct
_del_list {
const
char
*File_to_Del[FILE_DEL_MAX];
// 待删文件列表
int
count;
// 待删文件数量
struct
_del_attr Attr;
// 删除选项
} Delete_list = {0};
int
main(
int
argc,
const
char
*argv[])
{
int
i;
char
cmd;
if
(argc == 1)
{
help();
}
// 遍历参数
for
(i = 1; i < argc; i++)
{
// 获取参数选项
if
( *argv[i] ==
'-'
)
// 如果当前指向字符串的第一个为 '-'
{
if
(
strcmp
( argv[i],
"-f"
) == 0 )
{
Delete_list.Attr.Force_del = 1;
}
}
else
// 保存文件路径
{
Delete_list.File_to_Del[Delete_list.count] = argv[i];
// printf("待删文件路径 [%s] 已保存\n", Delete_list.File_to_Del[Delete_list.count]);
Delete_list.count++;
// printf("目前待删文件数目 %d\n", Delete_list.count);
}
}
// 遍历待删文件列表
for
(i = 0; i < Delete_list.count; i++)
{
if
( Delete_list.Attr.Force_del == 1 )
// 是否强制删除
{
if
( !DeleteFile( Delete_list.File_to_Del[i] ) )
// 如果非零则删除失败
{
printf
(
"删除文件错误:%x\n"
,GetLastError());
}
}
else
{
// 询问是否删除
printf
(
"是否要删除文件 [%s] ?(y/n)"
, Delete_list.File_to_Del[i] );
scanf
(
"%c"
, &cmd);
getchar
();
// 回收回车
if
( cmd ==
'y'
)
{
if
( !DeleteFile( Delete_list.File_to_Del[i] ) )
// 如果非零则删除失败
{
printf
(
"删除文件错误:%x\n"
,GetLastError());
}
else
{
printf
(
"文件 [%s] 已删除\n"
, Delete_list.File_to_Del[i]);
}
}
}
}
}
void
help()
{
printf
(
"删除文件:\n"
);
printf
(
"rm <文件名1> <文件名2> ... [-选项]\n"
);
printf
(
"选项: -f 强制删除"
);
}
|
写好之后,生成exe,打开cmd 切换到该目录下测试一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
Microsoft Windows [版本 6.1.7601]
版权所有 (c) 2009 Microsoft Corporation。保留所有权利。
# 切换目录
C:\Users\Lellansin>
cd
F:\Workspace\C\
rm
\Debug
# 切换盘符
C:\Users\Lellansin>F:
# dir 查看文件列表
F:\Workspace\C\
rm
\Debug>
dir
04
/24/2013
09:31 PM
04
/24/2013
09:31 PM
04
/24/2013
09:26 PM 32,256
rm
.exe
04
/24/2013
09:26 PM 231,948
rm
.ilk
04
/24/2013
09:26 PM 412,672
rm
.pdb
# 调用我们写的help函数
F:\Workspace\C\
rm
\Debug>
rm
删除文件:
rm
<文件名1> <文件名2> ... [-选项]
选项: -f 强制删除
# touch 创建两个文件
F:\Workspace\C\
rm
\Debug>
touch
1.txt 2.txt
F:\Workspace\C\
rm
\Debug>
dir
04
/24/2013
09:32 PM
04
/24/2013
09:32 PM
04
/24/2013
09:32 PM 0 1.txt
04
/24/2013
09:32 PM 0 2.txt
04
/24/2013
09:26 PM 32,256
rm
.exe
04
/24/2013
09:26 PM 231,948
rm
.ilk
04
/24/2013
09:26 PM 412,672
rm
.pdb
# 调用我们写的函数来删除刚刚的两个文件
F:\Workspace\C\
rm
\Debug>
rm
1.txt 2.txt
是否要删除文件 [1.txt] ?(y
/n
)y
文件 [1.txt] 已删除
是否要删除文件 [2.txt] ?(y
/n
)y
文件 [2.txt] 已删除
# 查看发现文件已删除
F:\Workspace\C\
rm
\Debug>
dir
04
/24/2013
09:32 PM
04
/24/2013
09:32 PM
04
/24/2013
09:26 PM 32,256
rm
.exe
04
/24/2013
09:26 PM 231,948
rm
.ilk
04
/24/2013
09:26 PM 412,672
rm
.pdb
|
那么大家在修改测试的时候只需要使用vs的生成功能(F7)即可,只不过要注意,如果修改代码生成没改变请使用 “重新生成” 选项(Ctrl+Alt+F7)。测试好了之后,确认功能实现无误,就可以把这个rm.exe拷贝到我们的 F:\mytools 下了。哟西,又多了一个工具。
按照前两个的流程我们可以很简单的过一下,查查资料,然后写个小工具简单的尝试一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 文件复制
BOOL
WINAPI CopyFile(
_In_
LPCTSTR
lpExistingFileName,
// 源文件名(路径)
_In_
LPCTSTR
lpNewFileName,
// 新文件名(路径)
_In_
BOOL
bFailIfExists
// 是否避免覆盖
);
# 文件剪切
BOOL
WINAPI MoveFile(
_In_
LPCTSTR
lpExistingFileName,
// 原文件名(路径)
_In_
LPCTSTR
lpNewFileName
// 新文件名(路径)
);
# 查找目录下的第一个文件
HANDLE
WINAPI FindFirstFile(
_In_
LPCTSTR
lpFileName,
// 待查目录
_Out_ LPWIN32_FIND_DATA lpFindFileData
// 找到的文件(输出参数)
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
#include
#include
#include
int
isDirectory(
char
*path);
void
help();
int
main(
int
argc,
char
const
*argv[])
{
char
file_src[MAX_PATH]={0};
char
file_dest[MAX_PATH]={0};
// 只输入程序名 和一个参数则调用help
if
(argc <= 2)
{
help();
return
0;
}
memmove
(file_src, argv[1],
strlen
(argv[1]));
memmove
(file_dest, argv[2],
strlen
(argv[2]));
if
( isDirectory(file_dest) )
{
// 如果第二个参数是目录, 则拼装新的文件路径
sprintf
(file_dest,
"%s\\%s"
, file_dest, file_src);
}
if
( CopyFile(file_src, file_dest, 0) == 0)
printf
(
"文件复制失败!"
);
return
0;
}
// 判断是否为目录
BOOL
isDirectory(
char
*path)
{
WIN32_FIND_DATA fd;
BOOL
rel = FALSE;
char
*p = path;
// 查找到第一个文件的句柄
HANDLE
hFind = FindFirstFile(path, &fd);
while
(*p !=
'\0'
) p++;
// 如果结尾是这两种符号就肯定是目录
if
( *(--p) ==
'\\'
|| *(p) ==
'/'
) {
*p =
'\0'
;
return
TRUE;
}
// 判断是否获取错误
if
(hFind != INVALID_HANDLE_VALUE)
{
// 文件信息按位与上目录属性, 非目录则全部置零
if
( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
{
rel = TRUE;
}
// 关闭查找句柄
FindClose(hFind);
}
return
rel;
}
void
help()
{
printf
(
"复制文件:\n"
);
printf
(
"cp <文件名> <新路径>\n"
);
printf
(
"cp <文件名> <新文件名>\n"
);
printf
(
"cp <文件名> <新路径\\新文件名>"
);
}
|
类似的,只要改动一行代码就可以写出移动文件的程序来了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
#include
#include
#include
int
isDirectory(
char
*path);
void
help();
int
main(
int
argc,
char
const
*argv[])
{
char
file_src[MAX_PATH]={0};
char
file_dest[MAX_PATH]={0};
// 只输入程序名 和一个参数则调用help
if
(argc <= 2)
{
help();
return
0;
}
memmove
(file_src, argv[1],
strlen
(argv[1]));
memmove
(file_dest, argv[2],
strlen
(argv[2]));
if
( isDirectory(file_dest) )
{
// 如果第二个参数是目录, 则拼装新的文件路径
sprintf
(file_dest,
"%s\\%s"
, file_dest, file_src);
}
if
( MoveFile(file_src, file_dest) == 0)
printf
(
"文件剪切失败!"
);
return
0;
}
// 判断是否为目录
BOOL
isDirectory(
char
*path)
{
WIN32_FIND_DATA fd;
BOOL
rel = FALSE;
char
*p = path;
// 查找到第一个文件的句柄
HANDLE
hFind = FindFirstFile(path, &fd);
while
(*p !=
'\0'
) p++;
// 如果结尾是这两种符号就肯定是目录
if
( *(--p) ==
'\\'
|| *(p) ==
'/'
) {
*p =
'\0'
;
return
TRUE;
}
// 判断是否获取错误
if
(hFind != INVALID_HANDLE_VALUE)
{
// 文件信息按位与上目录属性, 非目录则全部置零
if
( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
{
rel = TRUE;
}
// 关闭查找句柄
FindClose(hFind);
}
return
rel;
}
void
help()
{
printf
(
"剪切文件:\n"
);
printf
(
"mv <文件名> <新路径>\n"
);
printf
(
"mv <文件名> <新文件名>\n"
);
printf
(
"mv <文件名> <新路径\\新文件名>"
);
}
|
如果读者有跟着一起写的话,那么现在环境变量中应该已经有了4个小程序,那么我们现在来对这几个小程序做下测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
# 新建目录 test
C:\>
mkdir
test
# 切换
C:\>
cd
test
# 创建两个文件
C:\
test
>
touch
hello.txt list.txt
# 导入一些数据进去
C:\
test
>
echo
"hello world"
> hello.txt &&
dir
> list.txt
# 查看当前文件下的列表
C:\
test
>
dir
04
/26/2013
12:16 AM
04
/26/2013
12:16 AM
04
/26/2013
12:16 AM 17 hello.txt
04
/26/2013
12:16 AM 352 list.txt
# 复制hello.txt的副本hello2.txt
C:\
test
>
cp
hello.txt hello2.txt
# 剪切hello.txt 到 hi.txt (相当于重命名)
C:\
test
>
mv
hello.txt hi.txt
C:\
test
>
dir
04
/26/2013
12:17 AM
04
/26/2013
12:17 AM
04
/26/2013
12:16 AM 17 hello2.txt
04
/26/2013
12:16 AM 17 hi.txt
04
/26/2013
12:16 AM 352 list.txt
# 强制删除3个文件
C:\
test
>
rm
hello2.txt hi.txt list.txt -f
C:\
test
>
dir
04
/26/2013
12:18 AM
04
/26/2013
12:18 AM
C:\
test
>
|
那么博主就做了这些测试,实际上 cp 和 mv 的第二个参数可以直接给一个不同的路径名的,比如cp 1.txt ../ 就是将1.txt复制到上级目录。("."是当前目录, ".."是上级目录)
1
2
3
4
5
6
7
8
|
#读取文件内容
BOOL
WINAPI ReadFile(
_In_
HANDLE
hFile,
_Out_
LPVOID
lpBuffer,
_In_
DWORD
nNumberOfBytesToRead,
_Out_opt_
LPDWORD
lpNumberOfBytesRead,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);
|
那么这里带大家写一个类似 CMD 中 type 命令的程序,功能是读取一个文件并在 CMD 上显示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
#include
#include
void
help();
int
main(
int
argc,
char
const
*argv[])
{
HANDLE
hFile;
DWORD
fileSize, readSize;
char
*buffer;
if
(argc == 2)
{
hFile = CreateFile(
argv[1],
// 文件名
GENERIC_READ,
// 读取权限
0,
// 阻止其他进程访问
NULL,
// 子进程不可继承本句柄
OPEN_EXISTING,
// 仅当该文件或设备存在时,打开它
FILE_ATTRIBUTE_NORMAL,
// 普通文件
NULL);
// 不适用模板文件
if
(hFile == INVALID_HANDLE_VALUE)
{
printf
(
"无法打开文件 \"%s\"\n"
, argv[1]);
return
;
}
fileSize = GetFileSize(hFile,NULL);
// 获取文件大小
buffer = (
char
*)
malloc
(fileSize + 1);
// 获取一块内存
buffer[fileSize] =
'\0'
;
// 设置结尾
ReadFile(
hFile,
// 文件句柄
buffer,
// 读取到的文件所存放的缓冲区
fileSize,
// 要读取的字节数
&readSize,
// 实际读取的字节数
NULL
// 用 FILE_FLAG_OVERLAPPED 打开时所需的
);
printf
(buffer);
CloseHandle(hFile);
free
(buffer);
}
else
{
help();
}
return
0;
}
void
help()
{
printf
(
"浏览文件:"
);
printf
(
"cat <文件名>"
);
}
|
1
2
3
4
5
6
7
8
9
10
11
|
# 获取当前目录
DWORD
WINAPI GetCurrentDirectory(
_In_
DWORD
nBufferLength,
// 保存目录的变量大小
_Out_
LPTSTR
lpBuffer
// 保存目录的变量
);
# 获取下一个文件
BOOL
WINAPI FindNextFile(
_In_
HANDLE
hFindFile,
// 相对文件句柄
_Out_ LPWIN32_FIND_DATA lpFindFileData
// 获取文件信息(输出参数)
);
|
那么这里博主带大家写个类似 dir 这样 cmd 命令的程序,功能就是列举出当前目录下的文件。依旧是空项目,项目名为 ls ,新建一个 .c 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#include
#include
#include
int
main(
int
argc,
const
char
*argv[])
{
CHAR
path[MAX_PATH] = {0};
// 待列路径
CHAR
szFilePath[MAX_PATH];
// 具体查找路径
HANDLE
hListFile;
// 获取到的文件句柄
WIN32_FIND_DATA fileData;
// 查找到的文件数据
if
(argc == 1)
// 只输入程序名则显示当前目录
{
GetCurrentDirectory(MAX_PATH,path);
}
else
if
(argc == 2)
// 指定显示目录则显示指定目录
{
memmove
(path, argv[1],
strlen
(argv[1]));
}
// 复制路径到具体查找路径
lstrcpy(szFilePath, path);
// 路径拼接通配符
lstrcat(szFilePath,
"\\*"
);
// printf("列表路径: [%s] \n", szFilePath);
// 查找路径下第一个文件/目录,获得句柄
hListFile = FindFirstFile(szFilePath,&fileData);
// 判断句柄是否获取到
if
(hListFile == INVALID_HANDLE_VALUE)
{
printf
(
"错误:%d"
,GetLastError());
return
1;
}
else
{
do
{
// 输出找到的文件名
printf
(
"%s\t"
, fileData.cFileName );
}
// 查找下一个文件, 并获取其信息
while
(FindNextFile(hListFile, &fileData));
}
return
0;
}
|
cmd 测试一下:
1
2
3
4
5
6
7
8
|
# 列出当前目录下的文件
F:\Workspace\C\
ls
\Debug>
ls
. ..
ls
.exe
ls
.ilk
ls
.pdb
# 列出其他目录下的文件
F:\Workspace\C\
ls
\Debug>
ls
F:\Workspace\C
. ..
cp
hello
ls
mv
Project Project2
rm
Snake strcoll strxfrm Study Test
touch
ScanPort
|
那么一些基本的添加删除功能的工具集就这样做好了,是否稍稍有点成就感?实际上这些程序中还有很多可以改进的地方。
1.touch 中如果新建 touch F:\test\1.txt 如果test文件夹不存在的话,那么文件1.txt就不能被创建,我们可以加个"-p" 参数简单的用CreateDirectory函数来改进这一功能。
2.rm 和 mv 还只能移动文件,不能移动文件夹,那么我们的目录操作也可以解决这个问题了。通过判断参数是不是目录,然后新建一个目标目录,再通过ls中FindNextFile的形式一个个删除或剪切过去。
3.通配符的问题,比如 rm 和 ls 这样的命令可以考虑添加 rm *.txt -f (强制删除所有txt文件) 还有 ls *.txt (列举所有txt文件) 这样方便的通配符(“*”号匹配任意字符)的情况来删除。
4.ls 命令不能区别文件和文件夹(可以给文件夹改个颜色:使用SetConsoleTextAttribute函数),还可以加个 "-l" 参数来将列表竖起来看,可以加个 "-a" 将一些特出的隐藏文件都显示出来。
这些都是可以改进的地方。读者可以自行尝试着来改进这些程序。相信有的读者也应该发现了,博主这里是在windows平台下实现linux下的一些常见命令^_^~ 嘛,算是一个系列吧,以后也会把这些改进之后的程序源码公开到网上。
应该能改感觉到一点端倪,流程很简单:
想实现功能 --> 查找相应功能的API --> 收集详细资料 --> 使用该API
实际上 windows API 说白了就是这样,是对微软公司在windows系统平台上提供的系列函数的一个应用而已,过程都这样,查手册、收集资料神马的有的时候反而是最花时间的,至于难度,基本上不用担心API,依旧要注意的是C/C++本身的问题。
至于后来即将研究的进程、线程等,重点也是理解其编程的思想,因为最复杂的部分都已经被封装好了,我们只需要去调用API,学会如何使用罢了。
http://www.lellansin.com/windows-api%E6%95%99%E7%A8%8B%EF%BC%88%E4%B8%89%EF%BC%89-%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.html