正如我们在前一节看到的,任何文件可以有多个目录项指向它的i-node。我们创建一个已存在的文件的链接的方法是使用link函数。
- #include <unistd.h>
-
- int link(const char *existingpath, const char *newpath);
-
- 成功返回0,失败返回-1。
这个函数创建一个新的目录项newpath,指向已存在文件exsitingpath。如果newpath已存在,则返回一人错误。只有newpath的最后一个部分被创建。路径的其余部分必须已经存在。
新目录项的创建和链接接的增加必须是一个原子操作。(回想下3.11节关于原子操作的讨论。)
多数实现需要两个路径名在同一个文件系统,尽管POSIX.1允许一个实现支持跨越文件系统的链接。如果一个实现支持目录硬链接的创建,它只能由超级用户 完成。原因是这样做可能会引起文件系统的回路,多数处理文件系统的工具没有能力处理。(我们在4.16节展示由一个符号链接引起回路的一个例子。)许多文 件系统实现因为这个原因不允许目录的硬链接。
为了删除一个已存在的目录项,我们调用unlink函数。
- #include <unistd.h>
-
- int unlink(const char *pathname);
-
- 成功返回0,错误返回-1。
函数删除目录项并减少pathname引用的文件的链接数。如果文件有其它链接,文件的数据仍可以通过别的链接访问。如果错误发生,文件不会改变。
我们已经提过在解链接文件之前,我们必须有包含该目录项的目录的写权限和执行权限,因为这是我们正删除的目录项。同样,我们在4.10节也提过如果目录设置了粘滞位,我们必须有这个目录的写权限和以下三者之一:拥有该文件、拥有该目录、有超级用户权限。
只有当链接数减为0时文件的内容才能删除。另一个阻止文件内容被删除的条件是:只要有一些进程正打开该文件,它的内容不能被删除。
当一个文件被关闭后,内核首先检查打开该文件的进程数量。如果进程数为0,内核检查链接数,如果链接数为0,文件内容被删除。
看下面的代码:
- #include <fcntl.h>
-
- int
- main(void)
- {
- if (open("tempfile", O_RDWR) < 0)
- exit(1);
- if (unlink("tempfile") < 0)
- exit(1);
- printf("file unlinked\n");
- sleep(15);
- printf("done\n");
- exit(0);
- }
首先看看文件的大小
$ ls -l tempfile
-rw-r----- 1 tommy tommy 8483258 2012-02-22 16:21 tempfile
再看看可用的剩余空间
tommy@tommy-zheng-ThinkPad-T61:~/code/c/unix$ df /home
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/sda1 73843936 9101884 60990936 13% /
后台方式执行程序
$ ./a.out &
[1] 4991
$ file unlinked
查看文件被unlinked后文件是否存在
ls -l tempfile
ls: 无法访问tempfile: 没有那个文件或目录
查看unlink后可用的空间(没有变化)
$ df /home
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/sda1 73843936 9101884 60990936 13% /
程序运行完毕,查看文件关闭后可用的空间(已释放文件内容)
$ done
df /home
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/sda1 73843936 9101816 60991004 13% /
unlink的属性通常被程序用来确保当崩溃的,它创建的临时文件不会留在那里。进程用open或creat创建一个文件然后立即调用unlink。尽管 如此,文件并没有被删除,因为它仍被打开。只有当进程关闭这个文件或终止时(导致内核关闭它的所有的打开文件),文件才会被删除。
如果路径名是一个符号链接,unlink删除这个符号链接,而不是链接指向的文件。没有给定链接名删除其指向的文件的函数。
超级用户可以用目录的路径名调用unlink,但是函数rmdir函数应该被用来解链接一个目录,而不是unlink。我们在4.20节讲述rmdir函数。
我们还可以用remove函数解链接一个文件或目录。对于一个文件,remove与unlink相同。对于一个目录,remove和rmidir相同。
- #include <stdio.h>
-
- int remove(const char *pathname);
-
- 成功返回0,失败返回-1。
ISO C规定了remove函数来删除一个文件。名字从历史上的UNIX名字unlink改变是因为那时实现C标准的多数非UNIX系统都不支持文件链接的概念。
一个文件或目录可以用rename函数来重命名。
- #include <stdio.h>
-
- int rename(const char *oldname, const char *newname);
-
- 成功返回0,失败返回-1。
这个函数被ISO C为文件定义。(C标准没有处理目录。)POSIX.1扩展了这个定义,包含了目录和符号链接。
根据oldname是指向一个文件、目录还是符号链接,我们有几种条件来讲述下。我们必须说下如果newname已经存在时会发生什么:
1、如果oldname指定一个不是目录的文件,那我们就是重命名一个文件或符号链接。在这种情况下,如果newname已经存在,它不能是一个目录。如 果newname已存在且不是一个目录,那么它会被删除,而且oldname会重命名为newname。我们必须有包含oldname的目录和包含 newname的目录的写权限,因为我们改变了两个目录。
2、如果oldname指向一个目录,那么我们正在重命名一个目录。如果newname存在的话,它必须是一个目录,而且它必须为空。(当我们说目录为空 时,我们指这个目录里的项只有点和点点。)如果newname存在且为空时,它被删除,oldname被重命名为newname。额外地,当我们重命名一 个目录时,newname不能包含以oldname为前缀的路径。比如,我们不能重命名/usr/foo为/usr/foo/testdir,因为旧名字 (/usr/foo)是新名字的路径前缀,且不能被删除。
3、如果oldname或newname指向一个符号链接,链接本身被处理,而不是它解析的文件。
4、作为一个特殊例子,如果oldname和newname是同一个文件,那么函数成功返回但不改变任何东西。
如果newname已经存在,我们需要有删除它的权限。同样,因为我们正在删除oldname的目录项而且可能创建newname的目录项,我们需要包含旧文件所在目录和新文件所在目录的写权限和执行权限。
C语言rename()函数:重命名文件或目录
头文件:
1
|
#include <stdio.h>
|
函数rename()用于重命名文件、改变文件路径或更改目录名称,其原型为
1
|
int
rename
(
char
* oldname,
char
* newname);
|
【参数】oldname为旧文件名,newname为新文件名。
【返回值】修改文件名成功则返回0,否则返回-1。
重命名文件:
- 如果newname指定的文件存在,则会被删除。
- 如果newname与oldname不在一个目录下,则相当于移动文件。
重命名目录:
- 如果oldname和oldname都为目录,则重命名目录。
- 如果newname指定的目录存在且为空目录,则先将newname删除。
- 对于newname和oldname两个目录,调用进程必须有写权限。
- 重命名目录时,newname不能包含oldname作为其路径前缀。例如,不能将/usr更名为/usr/foo/testdir,因为老名字( /usr/foo)是新名字的路径前缀,因而不能将其删除。
【实例】一个简单的修改文件名的程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include<stdio.h>
#include <fcntl.h>
int
main(
void
)
{
char
oldname[100], newname[100];
/* prompt for file to rename and new name */
printf
(
"请告诉我一个文件的完整路径: "
);
gets
(oldname);
printf
(
"您想修改为: "
);
gets
(newname);
/* 更改文件名 */
if
(
rename
(oldname, newname) == 0)
printf
(
"已经把文件 %s 修改为 %s.\n"
, oldname, newname);
else
perror
(
"rename"
);
return
0;
}
|
运行结果:
1
2
3
|
请告诉我一个文件的完整路径:test.ncb
您想修改为:test111.ncb
已经把文件test. ncb修改为test111. ncb
|
例子首先定义两个数组存储用户指定的文件名, 接着使用函数gets()接收用户输入的文件名,再使用函数rename()修改,如果成功则返回值为0,提示修改成功。
注意:实际开发中尽量避免使用gets()函数,gets()会影响程序的安全性和健壮性,请查看:C语言gets()函数:从流中读取字符串
又如,设计一个在DOS命令行下修改文件的程序。
复制纯文本新窗口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <stdio.h>
void
main(
int
argc,
char
**argv)
{
if
(argc < 3)
{
printf
(
"Usage: %s old_name new_name\n"
, argv[0]);
return
;
}
printf
(
"%s=>%s"
, argc[1], argv[2]);
if
(
rename
(argv[1], argv[2]) < 0)
printf
(
"error!\n"
);
else
printf
(
"ok!\n"
);
}
|
C语言remove()函数:删除文件或目录
头文件:
1
|
#include <stdio.h>
|
remove()函数用于删除指定的文件,其原型如下:
1
|
int
remove
(
char
* filename);
|
【参数】filename为要删除的文件名,可以为一目录。如果参数filename 为一文件,则调用unlink()处理;若参数filename 为一目录,则调用rmdir()来处理。
【返回值】成功则返回0,失败则返回-1,错误原因存于errno。
错误代码:
EROFS 欲写入的文件为只读文件。
EFAULT 参数filename 指针超出可存取内存空间。
ENAMETOOLONG 参数filename 太长。
ENOMEM 核心内存不足。
ELOOP 参数filename 有过多符号连接问题。
EIO I/O 存取错误。
【实例】下面的程序演示了如何使用remove()函数删除文件。
1
2
3
4
5
6
7
8
9
10
|
#include<stdio.h>
int
main(){
char
filename[80];
printf
(
"The file to delete:"
);
gets
(filename);
if
(
remove
(filename) == 0 )
printf
(
"Removed %s."
, filename);
else
perror
(
"remove"
);
}
|
运行上述程序,首先声明用于保存文件名的字符数组变量,从控制台获取文件名,然后删除该文件,并根据删除结果输出相应的提示信息。
注意:实际开发中尽量避免使用gets()函数,gets()会影响程序的安全性和健壮性
文件的访问时间和修改时间可以用utime函数改变。
- #include <utime.h>
-
- int utime(const char *pathname, const struct utimebuf *times);
-
- 成功返回0,失败返回-1。
这个函数使用的结构体为:
- struct utimbuf {
- time_t actime; /* 访问时间 */
- time_t modtime; /* 修改时间 */
-
- };
在这个结构体里的两个时间值是日期时间,正如1.10节描述的那样,是从Epoch开始的秒数。
这个函数的操作和执行它所需的权限,取决于时间参数时否为NULL。
1、如果times为null指针,访问时间和修改时间都被设为当前时间。要这样做的话,进程的有效有用ID必须和文件的属主ID相同,或者进程必须有这个文件的写权限。
2、如果times是一个非空指针,访问时间和修改时间设为times指向的结构体的值。在这种情况下,进程的有效用户ID必须和文件的属主ID相同,或者进程必须是一个超级用户进程。仅有文件的写权限是不够的。
注意我们不能为状态改变时间指定一个值,st_ctime--i-node最近修改的值--因为这个域utime函数被调用时会被自动更新。
UNIX系统的一些实现,touch命令使用了这个函数。还有,标准存档程序,tar和cpio,选择性地调用utime来把文件的时间设为文件被存档的时间。
下面的程序使用O_TRUNC选项的open函数把文件裁切成0长度,但不改变它们的访问或修改时间。要这样做,程序首先通过stat函数得到时间,接着裁切文件,然后用utime函数重设时间:
- #include <fcntl.h>
- #include <utime.h>
-
- int
- main(int argc, char *argv[])
- {
- int i, fd;
- struct stat statbuf;
- struct utimbuf timebuf;
-
- for (i = 1; i < argc; i++) {
- if (stat(argv[i], &statbuf) < 0) {
- printf("%s: stat error\n", argv[i]);
- continue;
- }
- if ((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0) {
- printf("%s: open error\n", argv[i]);
- continue;
- }
- close(fd);
- timebuf.actime = statbuf.st_atime;
- timebuf.modtime = statbuf.st_mtime;
- if (utime(argv[i], &timebuf) < 0) {
- printf("%s: utime error\n", argv[i]);
- continue;
- }
- }
- exit(0);
- }
$ ls -l tmp
-rw-rw-r-- 1 tommy tommy 896467 2012-02-22 20:35 tmp
$ ls -lu tmp
-rw-rw-r-- 1 tommy tommy 896467 2012-02-22 20:29 tmp
$ ls -lc tmp
-rw-rw-r-- 1 tommy tommy 896467 2012-02-22 20:35 tmp
$ date
2012年 02月 22日 星期三 20:37:29 CST
$ ./a.out tmp
$ ls -l tmp
-rw-rw-r-- 1 tommy tommy 0 2012-02-22 20:35 tmp
$ ls -lu tmp
-rw-rw-r-- 1 tommy tommy 0 2012-02-22 20:29 tmp
$ ls -lc tmp
-rw-rw-r-- 1 tommy tommy 0 2012-02-22 20:37 tmp
正如我们预期的一样,最后修改时间和最后访问时间都没有改变。然而,状态改变时间改成了程序运行的时间。