用 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 是一个包含所有传递参数的字符串元素的数组:

argv0 -- 始终是程序名称 ./my_cat

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

argv2 -- 第二个参数(例如 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 实用程序的绝佳起点!

相关推荐
张小姐的猫11 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生11 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1113 小时前
linux基础IO
linux·运维·服务器
BelongPanda15 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯15 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983415 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器
yunwei3715 小时前
eBPF 教程:检查 exec 后真正安装的可执行镜像
linux·开源
笑锝没心没肺16 小时前
yum update 报错:GPG key retrieval failed: [Errno 14] HTTPS Error 404 - Not Found
linux·运维·服务器
AAA@峥16 小时前
CentOS Stream8 基于 Packstack 搭建 OpenStack 云平台全流程实战
linux·centos·openstack
汤永红16 小时前
gtk-x11-2.0.so.0
linux·运维·服务器