cat命令是最通用和最强大的工具。 它被认为是最常用的命令之一。 它具有与文本文件相关的三个功能:显示它们、组合它们的副本和创建新的副本。通过cat命令的深入学习,可以学习到文件操作的相关知识。

cat命令原理

在这里插入图片描述

cat命令的原理是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。

cat 的一般语法是

cat [选项] [文件名] [-] [文件名]

方括号表示所包含的项目是可选的。

cat 基本功能
在这里插入图片描述
命令 cat --help会得到它具体的命令行参数格式,通过不同的参数组合影响 cat 输出的格式。基本上所有的Linux命令都是用getopt函数来解析命令行参数的,cat也不例外。

在这里插入图片描述

读取文件

cat 最常见的用途是读取文件的内容,而 cat 通常是用于此目的的最方便的程序。
在这里插入图片描述

将显示名为 2021-11-14.log的文件的内容,与其他命令行程序的情况一样,cat 的标准输出(即输出的默认目标)是屏幕。 但是,它可以从屏幕重定向到要写入该文件的另一个文件或另一个命令以用作该命令的输入。

在下面的例子中,cat 的标准输出使用输出重定向操作符(用右尖括号表示)重定向到 file2:在这里插入图片描述

也就是说, cat 的输出被写入 file2 而不是显示在屏幕上。

标准输出可以使用管道重定向到过滤器以进行进一步处理。例如,如果文件太大而无法同时在监视器屏幕上显示所有文本,则文本将在屏幕上高速滚动并且非常难以阅读。这个问题很容易通过将输出管道输送到过滤器来轻松解决,即,

cat 2021-11-14.log | less

这允许用户通过按空格键一次一屏地前进文件的内容,并通过按 b 键向后移动。用户可以通过按 q 键退出less。

cat 的标准输入(即输入数据的默认源),与类 Unix 系统上的其他命令的情况一样,是键盘。也就是说,如果没有指定要打开的文件, cat 将读取键盘上输入的任何内容。

在同一行键入命令 cat 后跟输出重定向运算符和文件名,按 ENTER 移至下一行,然后键入一些文本,最后再次按 ENTER 会将文本写入该文件。因此,在以下示例中,在第二行键入的文本将写入名为 felines 的文件:
在这里插入图片描述
同时按下 ctrl + c 键终止程序。最后查看显示内容。

cat 级联

cat 的第二个作用是将文件内容的副本串联(即,串在一起)。因为连接只发生在副本上,所以对原始文件没有影响。

例如,以下命令将连接三个文件 file1、file2 和 file3 的内容副本:

cat file1 file2 file3

每个文件的内容将显示在监视器屏幕上(同样是标准输出,因此是没有重定向时的输出目的地)从新行开始,并按照文件名出现在命令。使用输出重定向运算符可以轻松地将此输出重定向到另一个文件,例如 file4,使用以下命令:

在这里插入图片描述
cat 的输出通过管道传输到排序过滤器,以便在串联之后和写入 file4 之前按字母顺序排列文本行:

cat file1 file2 file3 | sort > file4

cat 创建文件

cat 的第三个用途是创建文件。 对于小文件,这通常比使用 vi、gedit 或其他文本编辑器更容易。 它是通过键入 cat 后跟输出重定向运算符和要创建的文件的名称来完成的。

例如,可以通过键入来创建一个名为 file1 的新文件:

在这里插入图片描述
同时按下 ctrl + c 键终止程序。最后查看显示内容。

如果名为 file1 的文件已经存在,它将被具有相同名称的新空文件覆盖(即,其所有内容将被删除)。 因此,谨慎的用户可能更喜欢使用 append 运算符(由两个连续的向右尖括号表示),以防止意外擦除。

cat >> file1
在这里插入图片描述

也就是说,如果尝试使用 cat 和 append 运算符创建文件,并且新文件与现有文件具有相同的名称,则实际上将保留而不是覆盖现有文件,并且任何新文本 添加到现有文件的末尾。

你也还可以使用EOF保存下来。
在这里插入图片描述

创建文件后,要以EOF或STOP结束。创建11-14.txt文件,最后我们要EOF结束本次编辑。可以使用cat 11-14.txt查看一下文件的内容.。

如果我们想在文件末尾中追加文本信息,可以使用如下命令。
在这里插入图片描述
cat << EOF 选项在文件末尾显示一个结束标记。 它被称为 here 指令,文件内容将保存在给定的结束标记处。

该文件也可以在 'ctrl + d ’ 键的帮助下保存。 它的作用类似于结束标记。

cat 代码实现

int main(int argc, char *argv[])
{
	int ch;

	while ((ch = getopt(argc, argv, "benstuv")) != -1) 
	{
		switch (ch) {
		case 'b':
			bflag = nflag = 1;	/* -b implies -n */
			break;
		case 'e':
			eflag = vflag = 1;	/* -e implies -v */
			break;
		case 'n':
			nflag = 1;
			break;
		case 's':
			sflag = 1;
			break;
		case 't':
			tflag = vflag = 1;	/* -t implies -v */
			break;
		case 'u':
			setvbuf(stdout, NULL, _IONBF, 0);
			break;
		case 'v':
			vflag = 1;
			break;
		default:
			fprintf(stderr, "usage: %s [-benstuv] [file ...]\n",
			    *argv);
			return 1;
		}
	}
	argv += optind;
	/* 命令参数模式 */
	if (bflag || eflag || nflag || sflag || tflag || vflag)
		cook_args(argv);
	else
		raw_args(argv);
	if (fclose(stdout))
		err(1, "stdout");
	return rval;
}

void cook_args(char **argv)
{
	FILE *fp;

	if (*argv == NULL) 
	{
		cook_buf(stdin, "stdin");
		return;
	}

	for (; *argv != NULL; argv++) 
	{
		if (!strcmp(*argv, "-")) 
		{
			cook_buf(stdin, "stdin");
			clearerr(stdin);
			continue;
		}
		if ((fp = fopen(*argv, "r")) == NULL)  /* 打开文件*/
		{
			warn("%s", *argv);
			rval = 1;
			continue;
		}
		cook_buf(fp, *argv);
		fclose(fp);
	}
}
/*

映射的细节需要参考 ASCII 编码,基本思路是根据当前字符 ch 的值,在可打印字符范围内的直接输出,
不可打印字符则先根据预定的规则映射到一组可打印字符,在加一个前缀输出:

[0, 32): ^M (ch + 64),这一范围是控制字符,映射到 ch+64 的字符,并配上 ^ 前缀输出,比如 \r对应的 ASCII 码是13,显示出来就是 ^M,M的 ASCII码是 13 + 64 = 77。只有 \t 和 \n是例外。
[32, 127): 这个范围的字符是可打印字符,直接输出即可。
127: 这个字符的意义是 del,映射成 ^?
(127, 128 + 32): M-^(ch - 128 + 64)
[128+32, 128 + 127): M-(ch-128)
128 + 127: M-^?

*/
void cook_buf(FILE *fp, const char *filename)
{
	unsigned long long line;
	int ch, gobble, prev;

	line = gobble = 0;
	for (prev = '\n'; (ch = getc(fp)) != EOF; prev = ch) /* 循环直到输入文件末位 */
	{
		/* 空行功能的开关:根据上下文状态调整输出或者计数 */
		if (prev == '\n') 
		{
			/* -s 参数跳过连续的空行:比如输入文件有连续3行的空行,但只输出一个空行。
			自如忽略的空行也就不计算行号了,行尾的$自然也不比输出了。
			*/
			if (sflag) 
			{
				if (ch == '\n') 
				{
					if (gobble)
						continue;
					gobble = 1;
				} 
				else
					gobble = 0;
			}
			/* 用一个计数器计算\n的输出个数,每次输出当前行之前,先输出一个行号 */
			if (nflag)  /* -n参数,在每行行首输出行号 */
			{
				if (!bflag || ch != '\n') /* -b不对空行计算行号 */
				{
					fprintf(stdout, "%6llu\t", ++line);
					if (ferror(stdout))
						break;
				} 
				else if (eflag) 
				{
					(void)fprintf(stdout, "%6s\t", "");
					if (ferror(stdout))
						break;
				}
			}
		}
		if (ch == '\n') /* 如果指定了 -E 参数,则在每行行尾多输出一个$。逻辑上,在每次输出\n前,先输出一个$,再输出\n */
		{
			if (eflag && putchar('$') == EOF) 
				break;
		} 
		else if (ch == '\t') 
		{
			if (tflag) 
			{
				if (putchar('^') == EOF || putchar('I') == EOF)
					break;
				continue;
			}
		} 
		else if (vflag) 
		{
			if (!isascii(ch)) 
			{
				if (putchar('M') == EOF || putchar('-') == EOF)
					break;
				ch = toascii(ch);
			}
			if (iscntrl(ch)) 
			{
				if (putchar('^') == EOF ||
				    putchar(ch == '\177' ? '?' :
				    ch | 0100) == EOF)
					break;
				continue;
			}
		}
		if (putchar(ch) == EOF)
			break;
	}
	if (ferror(fp)) 
	{
		warn("%s", filename);
		rval = 1;
		clearerr(fp);
	}
	if (ferror(stdout))
		err(1, "stdout");
}

void raw_args(char **argv)
{
	int fd;
	/* 无参 */
	if (*argv == NULL) 
	{
		raw_cat(fileno(stdin), "stdin");
		return;
	}
	/* 有参 */
	for (; *argv != NULL; argv++) 
	{
		if (!strcmp(*argv, "-")) 
		{
			raw_cat(fileno(stdin), "stdin");
			continue;
		}
		if ((fd = open(*argv, O_RDONLY, 0)) == -1) 
		{
			warn("%s", *argv);
			rval = 1;
			continue;
		}
		raw_cat(fd, *argv);
		close(fd);
	}
}

void raw_cat(int rfd, const char *filename)
{
	int wfd;
	ssize_t nr, nw, off;
	static size_t bsize;
	static char *buf = NULL;
	struct stat sbuf;
	/* cat 的输入流由命令行给定, 默认是标准输入(stdin), 输出流是标准输出(stdout) */
	wfd = fileno(stdout); 
	if (buf == NULL) 
	{
		if (fstat(wfd, &sbuf) == -1)
			err(1, "stdout");
		bsize = MAXIMUM(sbuf.st_blksize, BUFSIZ);
		buf = (char *)malloc(bsize);
		if (buf == NULL)
			err(1, NULL);
	}
	while ((nr = read(rfd, buf, bsize)) != -1 && nr != 0) 
	{
		for (off = 0; nr; nr -= nw, off += nw) 
		{
			if ((nw = write(wfd, buf + off, nr)) == -1 || nw == 0)
				err(1, "stdout");
		}
	}
	if (nr == -1) 
	{
		warn("%s", filename);
		rval = 1;
	}
}

编译运行:

在这里插入图片描述
在这里插入图片描述

总结

cat命令在 Linux 中非常常用。 它从文件中读取数据并将其内容作为输出。 它帮助我们创建、查看、连接文件。

欢迎关注微信公众号【程序猿编码】,需要cat命令完整源码的添加本人微信号(c17865354792)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐