创建临时文件
经常在 /tmp 目录下看到许多带有奇怪编号的文件名,比如 XXX_2znzjy、XXX__6yQ1id 之类,却从未想过它们是怎么来的。
近来我一直在搞投机取巧的事情,就是用自己写的 C 程序直接调用 qhull 程序去生成三维点集的凸包。要达到这一目的,我首先需要解决我的程序与 qhull 程序之间的数据交换问题。在不触及 qhull 源代码的条件下,采用文件进行数据传递是我能找到的最好的方法(如果你有更好的,告诉我吧)。
选择使用文件进行数据交换之后,我想做的更加稳妥和专业一些,这时 /tmp 目录冲我暧昧地笑了一下。打开 APUE(版本号的默认值是“第 2 版”),在 5.13 节可以找到有关 ISO C 和 SUS 标准中用于生成临时文件的函数的详细阐述。话说,这本 APUE 自打我从图书馆借过来后,翻看了前两章之后,它就一直做充当我的枕头的工作,现在它总算又重新找回我的宠幸。
我比较喜欢 ISO C 定义的 tmpnam ( ) 函数,因为它可以生成一个唯一的文件名,我可以利用这个文件名打开文件进行各种操作。但是 "man -S 3 tmpnam",却看到:
Never use this function. Use mkstemp(3) or tmpfile(3) instead.
tmpfile ( ) 这个函数也很不错,不过不知道怎么获取它所创建的文件名。于是,我只能将希望寄托给 mkstemp () 了。
mkstemp ( ) 的确不辱使命,它可以创建临时文件,并且还能告诉我文件名。但是,它也不完美,因为它的容貌不讨人喜欢。首先,要使用它,需 #include <stdlib.h>,并且它不算是 c99 标准定义的函数,所以当你采用 c99 标准去调用 gcc 编译那些使用了 mkstemp ( ) 函数的 C 文件时,就会遇到“未找到 mkstemp ( ) 函数声明”的警告:
要消除这个警告,需要在 C 文件的头部添加:
如果是比较新的 glibc 版本,那么讲 _XOPEN_SOURCE 的值定义成 600 或 700 都可以。这些事情,都是 APUE 和 man 告诉我的。如果不想把代码搞的太难看,也可以在 gcc 的编译命令中添加 "-D_XOPEN_SOURCE" 宏选项来实现。
mkstemp () 函数接受一个字符串变量,返回文件描述符,如果返回值为 -1 表明创建文件失败。调用它的简单示例代码如下:
int fd;
if ((fd = mkstemp (fn)) == -1)
exit (-1);
注意,mkstemp ( ) 函数所接受的参数是字符串变量,而非字符串常量,并且该字符串变量的最后 6 位必须是 “XXXXXX”,也就是说这个字符串变量实际上是一个字符串模板,mkstemp ( ) 函数内部会将这个字符串的 "XXXXXX" 部分进行替换,并将替换后字符串作为临时文件名。
还要注意,mkstemp ( ) 函数返回的不是文件指针,而是文件描述符(参考 APUE 3.2 节)。如果想将文件描述符转换为我们熟悉的 C 语言文件指针类型,可以使用 fdopen ( ) 函数,例如:
char fn[128] = "/tmp/testXXXXXX";
int fd;
if ((fd = mkstemp (fn)) == -1)
exit (-1);
if ((fp = fdopen (fd)) == NULL)
exit (-1);
在拥有文件指针的情况下,我们就可以“很自由地”(相对于动态语言,一点都不自由)利用 ISO C 定义的文件操作函数进行文件读写操作了。
最后要注意的就是,mkstemp ( ) 不会对创建的临时文件进行删除。如果临时文件位于 /tmp 目录,那么系统重新启动时一般会自动清理。如果想在程序中删除不再使用的临时文件,可以使用 Unix 系统调用 unlink ( ) 函数来实现。unlink ( ) 函数的用法可以查阅 man 的第 2 节,即 "man -S 2 unlink"。
2009年8月22日 17:18
可以用管道交换数据。如果数据流是单向的,用popen会很简单;如果是双向的,就把qhull当作协同进程,用两个管道实现。
参考apue进程间通信的前四节。
2009年8月22日 18:45
想过用管道,但是是不是需要修改 qhull 的代码才可以实现呢?
2009年8月23日 00:11
不用修改,可以完全不了解qhull的代码。
下面是使用popen的例子。我不懂qhull,测试用的数据是rbox c d D2的结果(来自qhull自带的例子)。
#include <stdio.h>
int
main()
{
FILE *fp = popen("qhull", "w");
fprintf(fp, "2 rbox c d D2\n");
fprintf(fp, "8\n");
fprintf(fp, "-0.5 -0.5\n");
fprintf(fp, "-0.5 0.5\n");
fprintf(fp, "0.5 -0.5\n");
fprintf(fp, "0.5 0.5\n");
fprintf(fp, "0 -0.5\n");
fprintf(fp, "0 0.5\n");
fprintf(fp, "-0.5 0\n");
fprintf(fp, "0.5 0\n");
fclose(fp);
return 0;
}
其中
FILE *fp = popen("qhull", "w");
创建一个运行qhull的子进程,并用管道把子进程的标准输入和fp相连。然后就是向fp写入数据。
2009年8月23日 00:54
多谢,太好了!
看来,把 APUE 当枕头是严重错误的,继续翻