用 C 语言实现 cat 实用程序

大家好!我是大聪明-PLUS

刚开始学习 C 语言时,我对它与其他语言相比的"笨拙"感到很畏惧。一切都非常严格,需要手动控制,但这正是它吸引我的地方。感觉就像你在直接与系统对话。

有一天,我遇到了一个挑战:编写两个 Linux 实用程序:cat 和 grep。虽然它们看起来很简单,但却是一个深入研究文件操作,甚至粗略理解 C 语言工作原理和功能的绝佳机会。

在本文中,我将尝试解释和演示我的思考过程,以及为什么我现在对控制台命令有了完全不同的看法。在本文中,我将仅详细介绍 cat 实用程序的实现;您可以在我的另一篇文章中阅读有关编写 grep 的内容,我很快会提供链接。

这只猫是什么动物?

对我来说,Cat 似乎比 grep 更容易编写,而 grep 部分基于 cat - 它也从文件读取数据,它也处理字符串,但在过滤形式上有细微的差别。

首先我们要明白的是:

  • cat 不仅仅是"读取文件并打印"。它必须能够连续处理多个文件、对行进行编号(并将空行与非空行区分开)、折叠空行、显示制表符以及在行尾显示 $ 符号。所有这些功能都是通过 switch case 语句并为每个 case 分配逻辑来实现的。

  • 在 C 中,您将使用更高级别的 API(fopen、fclose、fgetc、fputs)------方法的选择非常重要。

  • 一切都必须手动完成:缓冲和错误检查;

  • 逐个字符地阅读而不是逐行地阅读很重要。

程序的总体结构

为了避免迷失在几十行代码中,我们将 cat 实用程序分为三个部分:

  1. 获取和解析标志

  2. 循环遍历所有文件

  3. 逐行(或更准确地说,逐个字符)输出并带有选项处理

复制代码
int` `main`(`int` `argc`, `char` `*argv`[]) {
  `Flags` `flags` `=` {`0`}; 
  `parsing_flags`(`argc`, `argv`, `&flags`); 
  `for` (`int` `i` `=` `optind`; `i` `<` `argc`; `++i`) { 
    `print_file`(`argv`[`i`], `flags`);
  }
  `return` `0`;
}`

这里,argc 是我们传递给程序的参数总数,包括实用程序本身的名称。重要的是要理解 argv 是一个包含所有传递参数的字符串元素的数组:

argv[0] -- 始终是程序名称 ./my_cat

argv[1] -- 程序的第一个参数(任何实用程序标志,例如 -n)

argv[2] -- 第二个参数(例如 file.txt,也可能有另一个标志)

等等。

标志 flags -- 我们声明所有标志(b、e、E、n、s、t、T、v)的结构体。需要将其清除,以便所有选项默认禁用,从而避免程序启动时出现问题。

在 for 循环中,我们根据传递给终端的标志来处理文件,其中 optind 是非选项参数的索引(例如,非 -n 、非 -e 等)。optind 实际上是一个全局变量,它会根据传递的参数递增。它很智能,知道何时停止,因此当它遇到文件时就会停止。这对于区分"作为选项传递给程序的内容"和"作为数据/文件等传递的内容"至关重要。

FLAG分析

在 cat 中,您需要实现执行我在一开始简要提到的各种功能的标志。

-n ---所有行的编号: 计算行数,在每行前显示一个数字;

-b ---**仅对非空行进行编号:**类似于 -n,但跳过空行;

-s ---**删除重复的空行:**保留有关前一个空行的标志;

-E -**在行尾显示 :**检查换行符,添加

-T - 显示制表符为 ^I**:**处理一行中的每个字符;

如您所见,我们同时拥有大标志和小标志。此外,大标志(-E, -T)的功能与小标志(-e, -t)相同,但略有不同。大标志使用辅助标志 进行操作-v,而小标志已包含辅助标志。

复制代码
while` ((`opt` `=` `getopt_long`(`argc`, `argv`, `"bEnstvT"`, `longopt`, `NULL`)) `!=` `-1`) { 
  `switch` (`opt`) { 
    `case` `'b'`:
	  `flags->b` `=` `1`;  
	`break`;       
    `case` `'e'`: 
	  `flags->e` `=` `1`; 
	  `flags->v` `=` `1`; 
	`break`;        
    `// и т.д.`
  }
}`

事实上getopt_long,你可以在 getopt 库的循环中看到它。当然,你也可以通过编写自己的解析器来省去它,但在我看来,它看起来更简洁。getopt-long 的本质在于它扩展了标准 getopt 函数的功能,同时支持长标志(-T, -E)和长选项(--help)。在这里,我专门用它来支持长标志。

读取文件和基本输出

为了在此阶段确定你的猫是否正在读取文件,你可以编写一个相当简单的函数:

复制代码
FILE` `*f` `=` `fopen`(`name`, `"r"`); 
`int` `c`;
`while` ((`c` `=` `fgetc`(`f`)) `!=` `EOF`) { 
  `putchar`(`c`); 
}
`fclose`(`f`);`

使用 调用fopen和读取模式r,我们打开文件。然后启动一个循环,查找int c需要捕获的字符。需要注意的是,我们严格执行到文件末尾(EOF),因为 C 语言比较"混乱",会在最后一个字符后存储各种垃圾数据。最后,当到达文件末尾时,我们还将所有文本逐个字符地输出到终端,并(f)使用关闭文件fclose

实际上,这段代码的六行代码中,我们只需要两行:打开文件和打印文件的循环条件。我们会将这些添加到标志逻辑实现中,然后根据需要操作文件。

标志逻辑

实现的最后阶段是标志逻辑。为了确保它们正确运行,我创建了 int 变量,根据标志的用途执行不同的功能。让我们分别看看每个选项。

**行号 (-n):**要对行进行编号,我们需要一个计数器和一个标志来指示新行的开始。当移动到新行时,我们只需增加计数器即可。

对非空行进行编号 (-b): 与 [-b] 最重要的区别-n在于,您需要对至少包含一个与 [-b] 不同的字符的行进行编号\n(这就是为什么我们使用 [-b] 逐个字符进行编号fgetc)。我们还使用一个标志来指示新行的开始,并指定条件的正确执行。

折叠空行 (-s): 这里我们只想打印一个连续的空行。我创建了一个变量 empty,用于统计连续的行数\n。每遇到一个空行,\n就增加 empty 的值。

复制代码
if` (`c` `==` `'\n'` `&&` `prev_ch` `==` `'\n'`) {
  `empty++`;
} `else` {
  `empty` `=` `0`;
}

`if` (`flags`.`s` `&&` `empty` `>` `1`) {
  `prev_ch` `=` `c`;`

显示行尾 (-E, -e): 如果条件包含标志-E,则在每行前打印一个 标记`\n`。使用 -e 选项效果相同,只是会添加一个`-v` 标记,用于显示不可见的字符(详见下文)。

制表符显示 (-T, -t): \t我们需要用序列替换字符^I。这里,我建议检查你的测试文件,确保它们包含制表符;有时它们可以用空格替换。空格不会被高亮显示,只有 TAB 会被高亮显示。

复制代码
if` (`flags`.`t` `&&` `c` `==` `'\t'`) {
  `printf`(`"^"`);
  `c` `=` `'I'`;
}`

显示控制(不可见)字符 (-v): 默认情况下,cat 会按文件原样显示所有内容。但实际上,文件可能包含某些特殊字符,例如.txt插入符号(可能看起来像这样)\n \r等等。如果只是将它们显示在屏幕上,您可能无法理解多余的空格或缩进来自哪里。因此,我们的目标是显示控制字符和带有代码的字符,例如:

复制代码
` `if` (`c` `==` `127`) {
    `printf`(`"^?"`);`

完成后,只需关闭文件fclose即可!Cat 写好了!

在实现 cat 实用程序的过程中,我意识到即使是简单的命令也隐藏着大量与文件、符号和内存相关的底层操作。曾经看似简单的"读取并打印"操作,实际上需要关注细节、理解语言原理以及透彻理解数据处理逻辑。现在,我不仅将 cat 视为一个实用程序,更是学习 C 语言和 Linux 实用程序的绝佳起点!

相关推荐
dessler3 小时前
Elasticsearch(ES)常用运维命令
linux·运维·elasticsearch
馨谙4 小时前
正则表达式完全指南:从入门到实战应用
linux·通配符
东亚_劲夫4 小时前
Linux线程
linux·运维
搬砖的小码农_Sky4 小时前
Ubuntu Server 命令行关机指南
linux·运维·ubuntu
zzzsde5 小时前
【Linux】基础指令(2):理解Linux的指令和核心概念
linux·运维·服务器
Empty_7775 小时前
Keepalived双机热备
linux·git·github
wdfk_prog12 小时前
[Linux]学习笔记系列 -- [kernel][time]alarmtimer
linux·笔记·学习
小志biubiu12 小时前
【Linux】Ext系列文件系统
linux·服务器·c语言·经验分享·笔记·ubuntu·操作系统
ha204289419412 小时前
Linux操作系统学习之---基于环形队列的生产者消费者模型(毛坯版)
linux·c++·学习