介绍一个Linux命令tee
它存在的作用:
比如我们运行一个程序,可以通过printf打印日志,然而终止代码之后想要保存日志的话能通过复制终端产生的日志保存到一个日志文件里面。或者在终端输入一个命令,比如sudo apt-get install vim产生的日志想要保存到一个文件里面,那么可以sudo apt-get install vim | tee -a a.c
这样安装vim产生的日志将保存到a.c文件里面。-a命令就是append追加的意思;不加-a默认是覆盖掉之前内容。如果想写入多个文件:tee [file1] [file2] [file3]
Linux C实现tee命令
- 读取参数,将所有参数打印出来
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]){
for (int i = 0; i < argc; i++) // argc意思就是arg(argument)的c(count)
{
printf("%s\n",argv[i]); // argv意思就是arg(argument)的v(value) 这里就是循环打印出来命令行输入参数的所有值
/* code */
}
return 0;
}
- tee程序最终功能
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
struct teeConfigStruct
{
int ap_n; // 此参数后面的所需参数个数
char *outputPath; // 要操作的文件
int outputFd;
} teeConfig;
void getArgs(int argc, char *const argv[])
{
int opt;
if ((opt = getopt(argc, argv, "a")) != -1)
{
teeConfig.ap_n = 0;
switch (opt)
{
case 'a':
printf("追加模式\n");
teeConfig.ap_n = 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.ap_n == 1)
{
outputFileFlag = O_RDWR | O_CREAT | O_APPEND;
}
else
{
outputFileFlag = O_RDWR | O_CREAT | O_TRUNC; //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");
}
}
首先我们打开标准输入和输出文件,然后,我们使用write和read分别对标准输入和目标文件进行操作,接着判断-a 设置的标志,如果为1,在读取输出文件的时候,则加上O_APPEND标签,否则,只是打开它。
Linux C实现cp命令
首先先了解一个空洞文件的概念
lseek的系统调用是可以改变在文件上面的偏移量的,而且还允许其超出文件的长度。偏移量一旦超出了文件的长度,下一次进行文件IO写入操作文件的时候便会延续偏移量的位置继续写入,进而在文件中间产生了空洞的部分,这部分会以”\0”填充,而从原来的文件结尾到新写入数据间的这段空间就被称为“文件空洞”。
文件空洞部分实际上是不会占用任何的物理空间的,直到在某个时刻对空洞部分进行写入文件内容的时候才会为它分配对应的空间。但是在空洞文件形成的时候,逻辑上面的文件大小是分配了空洞部分的大小的。
- 在创建虚拟机的时候,我们会使用img工具生成一个例如50GB大小的镜像文件,但是其实在安装完系统之后,镜像的大小可能只有4GB,也就是说img并不会马上就占用掉物理存储空间的50GB,而是在未来使用过程中不断增加的。
- 像在我们平时使用迅雷下载的时候,刚开始下载但是本地的下载文件就已经好几百兆了,这就利用了空洞文件。为了能够并行下载,创建空洞文件可以让多线程在不同的seek上面开始写入文件,如果不是空洞文件就只能串行写入了。
先创建一个空洞文件
dd if=/dev/urandom of=void.file bs=4096 seek=999 count=1
从/dev/urandom中读内容,写到void.file bs参数设置4096大小,seek设置偏移量,count设置写入的block数量为1,这条命令将产生长度为1000的数据块文件
ls -l查看文件大小为4M
-rw-rw-r-- 1 lk lk 4096000 12月 17 23:00 void.file
du -h void.file查看文件大小为4K
4.0K void.file
ls -l 显示的是实际文件(目录)大小
du(disk usage)磁盘使用量
为了验证cp拷贝空洞文件的效果,方法就是跟cat 重定向文件对比一下
cat void.file > void.cat
cp void.file void.cp
ls命令查看两个新生文件的大小
-rw-rw-r-- 1 lk lk 4.0M 12月 17 23:00 void.cat
-rw-rw-r-- 1 lk lk 4.0M 12月 17 23:00 void.cp
du查看
4.0M void.cat
4.0K void.cp
验证结果:
ls获取得到的是文件的逻辑大小,而du获取得到的是文件的实际占用物理块的大小。
cat命令重定向内容到新的文件的时候,其遇到了空洞部分,会用0来填充,这样空洞部分其实也就有了内容了,对应需要为他分配物理存储block,这样文件的真是大小其实就是4MB,但是cp命令在遇到空洞部分的时候,会模拟源文件的空洞调用seek,进行偏移之后再进行内容拷贝,这样其实生成的新文件和源文件是一样的,空洞部分都是不被分配真是存储空间的。
#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);
}