第四章
4-1 tee命令是从标准输入中读取数据,直至文件结尾,随后将数据写入标准输出和命令行参数所指定的文件。请使用I/O系统调用实现tee命令。默认情况下,若已存在与命令行参数指定文件名同名的文件,tee命令将其覆盖。或者,当使用-a命令时,则在同名文件后面追加数据。
我们可以使用tee --help命令来查看帮助。可以看到deepin中所显示的是中文帮助。我们要实现的是 -a指令。
$ tee --help
用法:tee [选项]... [文件]...
将标准输入复制到每个指定文件,并显示到标准输出。
-a, --append 内容追加到给定的文件而非覆盖
-i, --ignore-interrupts 忽略中断信号
-p diagnose errors writing to non pipes
--output-error[=MODE] set behavior on write error. See MODE below
--help 显示此帮助信息并退出
--version 显示版本信息并退出
这是本书第一次编程,我们先写一个程序来读取一下参数吧,下面这个程序可以将所有参数打印出来。
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]){
for(int i=0;i<argc;i++){
printf("%s", argv[i]);
}
}
题目中提到了可以使用getopt来获取参数。我们可以使用getopt来获取第一个参数,来判断是不是-a,如果是的话,我们将-a这个标记记到存储信息的结构体中。
#include <unistd.h>
#include <stdio.h>
struct teeConfigStruct
{
int a;
char *outputPath;
int outputFd;
} teeConfig;
int main(int argc, char * const argv[]){
int opt;
if ((opt = getopt(argc, argv, "a")) != -1)
{
teeConfig.a = 0;
switch (opt)
{
case 'a':
printf("追加模式\n");
teeConfig.a = 1;
break;
default:
printf("参数错误\n");
_exit(0);
break;
}
}
if (argc - optind < 1)
{
printf("参数太少\n");
_exit(0);
}
teeConfig.outputPath = argv[optind];
printf("输出路径 %s \n", teeConfig.outputPath);
}
运行这段程序,我们发现程序如预期运行。下面演示了四种情况,分别是:正常模式的正常输入,追加模式的正常输入,参数错误,参数太少。
gcc 4-1.c
$ ./a.out def
输出路径 def
$ ./a.out -a def
追加模式
输出路径 def
$ ./a.out -b
./a.out: invalid option -- 'b'
参数错误
$ ./a.out
参数太少
重构代码。
#include <unistd.h>
#include <stdio.h>
struct teeConfigStruct
{
int a;
char *outputPath;
int outputFd;
} teeConfig;
void getArgs(int argc, char *const argv[])
{
int opt;
if ((opt = getopt(argc, argv, "a")) != -1)
{
teeConfig.a = 0;
switch (opt)
{
case 'a':
printf("追加模式\n");
teeConfig.a = 1;
break;
default:
printf("参数错误\n");
_exit(0);
break;
}
}
if (argc - optind < 1)
{
printf("参数太少\n");
_exit(0);
}
teeConfig.outputPath = argv[optind];
printf("输出路径 %s \n", teeConfig.outputPath);
}
int main(int argc, char * const argv[]){
getArgs(argc, argv);
}
然后,我们完成tee的程序的最终功能。程序最重要的部分就是拷贝数据了,首先我们打开标准输入和输出文件,然后,我们使用write和read分别对标准输入和目标文件进行操作。为此,我们需要判断判断-a的标志,如果设置为1,我们在读取输出文件的时候,则加上O_APPEND标签,否则,只是打开它。
另外,如果文件不存在,我们需要自动创建文件,所以我们需要O_CREATE来打开文件。
此外,我们需要截断文件的结尾。在覆盖模式下,文件原来的内容比本次的要长,如果直接结束的话,原来剩余的内容仍然存在。我们需要加上O_TRUNC来帮助结束。
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
struct teeConfigStruct
{
int a;
char *outputPath;
int outputFd;
} teeConfig;
void getArgs(int argc, char *const argv[])
{
int opt;
if ((opt = getopt(argc, argv, "a")) != -1)
{
teeConfig.a = 0;
switch (opt)
{
case 'a':
printf("追加模式\n");
teeConfig.a = 1;
break;
default:
printf("参数错误\n");
_exit(0);
break;
}
}
if (argc - optind < 1)
{
printf("参数太少\n");
_exit(0);
}
teeConfig.outputPath = argv[optind];
printf("输出路径 %s \n", teeConfig.outputPath);
}
int main(int argc, char *const argv[])
{
getArgs(argc, argv);
int outputFileFlag;
if (teeConfig.a == 1)
{
outputFileFlag = O_RDWR | O_CREAT | O_APPEND;
}
else
{
outputFileFlag = O_RDWR | O_CREAT | O_TRUNC;
}
teeConfig.outputFd = open(teeConfig.outputPath, outputFileFlag, 0777);
if (teeConfig.outputFd == -1)
{
printf("无法打开输出文件!\n");
_exit(0);
}
char buff;
while (read(STDIN_FILENO, &buff, 1) > 0)
{
printf("%c", buff);
if (write(teeConfig.outputFd, &buff, 1) == -1)
{
printf("无法写入!\n");
_exit(0);
}
}
if (close(teeConfig.outputFd) == -1)
{
printf("无法关闭文件\n");
}
}
现在我们来测试一下。
$ gcc 4-1.c
$ ./a.out test
在这里输入一些字符,并按下回车换行。按下Ctrl+D来结束程序。
$ cat test
在这里可以看到输出,就是刚刚写入的内容。
$ ./a.out -a test
在这里追加字符。
$ cat test
这里会显示追加后的内容。
经过测试,大体行为和tee表现一致。
4-2 编写一个类似于cp命令的程序,当使用该程序复制一个包含空洞的文件时,能够使得目标文件和源文件内容保持一致,
这个程序思路比较简单,读取源文件的一个字符,如果是0的话,使用lseek右移一个文件偏移量。否则将字符写入目标文件中。
这似乎有一个问题,就是无法真正的区分源文件中的连续0或是真实的连续空洞。但是,系统中的cp命令也是这样实现的,并没有真正的做到复制空洞。
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
struct copyConfigStruct{
int inputFd;
int outputFd;
char * inputPath;
char * outputPath;
}copyConfig;
int main(int argc, char * const argv[]){
if(argc <3) {
printf("参数太少\n");
_exit(0);
}
copyConfig.inputPath = argv[1];
copyConfig.outputPath = argv[2];
copyConfig.inputFd = open(copyConfig.inputPath, O_RDONLY);
if(copyConfig.inputFd == -1){
printf("输入文件打开失败!\n");
_exit(0);
}
copyConfig.outputFd = open(copyConfig.outputPath, O_RDWR|O_TRUNC|O_CREAT, 0777);
if(copyConfig.outputFd == -1){
printf("输出文件打开失败!\n");
_exit(0);
}
char buff;
while( read(copyConfig.inputFd, &buff, 1) > 0 ){
if (buff == 0){
if(lseek(copyConfig.outputFd, 1, SEEK_CUR) == -1){
printf("跳过空洞失败!\n");
_exit(0);
}
}
else if(write(copyConfig.outputFd, &buff, 1) == -1){
printf("写入文件失败!\n");
_exit(0);
}
}
close(copyConfig.inputFd);
close(copyConfig.outputFd);
}
接下来进行测试。
$ dd if=/dev/urandom of=testfile bs=4096 seek=999 count=1
这句是创建一个大小为4M,占用空间4K的空洞文件testfile
$ gcc 4-2.c
$ ./a.out testfile output
$ ls -lh
$ du -ah
ls是显示文件大小,而du显示的是占用存储空间大小。可以看到两者文件是一致的。