当我们在命令行直接输入cat xxx 时,系统一般调用的是simple_cat 而不是cat ,故本文介绍simple_cat的调用过程
cat 主程序
1. 命令行解析
基本上全部的Linux命令都是用getopt函数来解析命令行參数的, cat也不例外, cat使用的是getopt_long函数, 以便解析长參数, 用一些bool变量来存储选项值。当没有参数时,使用默认参数。
/* Variables that are set according to the specified options. *///设置一些初始选项,基本上就是7个选项
bool number = false;
bool number_nonblank = false;
bool squeeze_blank = false;
bool show_ends = false;
bool show_nonprinting = false;
bool show_tabs = false;
int file_open_mode = O_RDONLY;
//option结构体供getopt函数使用
static struct option const long_options[] =
{
{"number-nonblank", no_argument, NULL, 'b'},
{"number", no_argument, NULL, 'n'},
{"squeeze-blank", no_argument, NULL, 's'},
{"show-nonprinting", no_argument, NULL, 'v'},
{"show-ends", no_argument, NULL, 'E'},
{"show-tabs", no_argument, NULL, 'T'},
{"show-all", no_argument, NULL, 'A'},
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
{NULL, 0, NULL, 0}
};
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
/* Arrange to close stdout if we exit via the
case_GETOPT_HELP_CHAR or case_GETOPT_VERSION_CHAR code.
Normally STDOUT_FILENO is used rather than stdout, so
close_stdout does nothing. */
atexit (close_stdout);
/* Parse command line options. */
int c;
// 在这里对输入的参数进行处理
while ((c = getopt_long (argc, argv, "benstuvAET", long_options, NULL))
!= -1)//当能够正常获得时进行更新
{
switch (c)
{
case 'b':
number = true;
number_nonblank = true;
break;
case 'e':
show_ends = true;
show_nonprinting = true;
break;
case 'n':
number = true;
break;
case 's':
squeeze_blank = true;
break;
case 't':
show_tabs = true;
show_nonprinting = true;
break;
case 'u':
/* We provide the -u feature unconditionally. */
break;
case 'v':
show_nonprinting = true;
break;
case 'A':
show_nonprinting = true;
show_ends = true;
show_tabs = true;
break;
case 'E':
show_ends = true;
break;
case 'T':
show_tabs = true;
break;
case_GETOPT_HELP_CHAR;//调用cat 的帮助手册
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);// 调用 cat -v 来显示cat 的版本
default:
usage (EXIT_FAILURE);//若输入的格式不对,则打印出正确用法并退出
}
}
2.获得文件属性,和一些基本信息,比如设备和文件序列号等
if (fstat (STDOUT_FILENO, &stat_buf) < 0) //读入状态,若未正常读入则退出
die (EXIT_FAILURE, errno, _("standard output"));
dev_t out_dev = stat_buf.st_dev;//设备
ino_t out_ino = stat_buf.st_ino;//文件序列号
bool out_isreg = S_ISREG (stat_buf.st_mode) != 0;//判断是否为常规文件
if (! (number || show_ends || squeeze_blank))
{
file_open_mode |= O_BINARY;
xset_binary_mode (STDOUT_FILENO, O_BINARY);
}//如果不用显示行号,结尾,以及,则用二进制打开
3.读取一次读写的字节数目
cat是以read和write函数为基础实现,故一次字节数的多少页影响了程序的性能。
outsize为一次写的字节数目。io_blksize会返回一个读写字节大小,(insize,在判断的过程中进行了更新)。
idx_t outsize = io_blksize (stat_buf);//读写字节的大小
4.主程序
进入主循环,进行一系列的判断(不做详解,其中包括了打开文件是否正常的判断,确定输入的insize大小以及对文件常规性的判断),
if (argind < argc)
infile = argv[argind];
bool reading_stdin = STREQ (infile, "-");
if (reading_stdin)
{
have_read_stdin = true;
input_desc = STDIN_FILENO;
if (file_open_mode & O_BINARY)
xset_binary_mode (STDIN_FILENO, O_BINARY);
}
else
{
input_desc = open (infile, file_open_mode);
if (input_desc < 0)
{
error (0, errno, "%s", quotef (infile));
ok = false;
continue;
}
}
if (fstat (input_desc, &stat_buf) < 0)
{
error (0, errno, "%s", quotef (infile));
ok = false;
goto contin;
}
/* Optimal size of i/o operations of input. */
idx_t insize = io_blksize (stat_buf);//确定输入的块大小
fdadvise (input_desc, 0, 0, FADVISE_SEQUENTIAL);
/* Don't copy a nonempty regular file to itself, as that would
merely exhaust the output device. It's better to catch this
error earlier rather than later. */
if (out_isreg
&& stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino
&& lseek (input_desc, 0, SEEK_CUR) < stat_buf.st_size)
{
error (0, 0, _("%s: input file is output file"), quotef (infile));
ok = false;
goto contin;
}
之后根据是否有参数来选择cat 的函数
- 若有参数则选择cat函数
- 若无参数则先进行copy_cat,若不成功则进行simple_cat。
if (! (number || show_ends || show_nonprinting
|| show_tabs || squeeze_blank))
{
int copy_cat_status =
out_isreg && S_ISREG (stat_buf.st_mode) ? copy_cat () : 0;
if (copy_cat_status != 0)
{
inbuf = NULL;
ok &= 0 < copy_cat_status;
}
else
{
insize = MAX (insize, outsize);
inbuf = xalignalloc (page_size, insize);
ok &= simple_cat (inbuf, insize);
}
}
else
{
//cat ....
}
5.simple_cat 函数
将buf中的bufsize大小的数据输出到屏幕(STDOUT_FILEND)
其中,试用n_read来记录已经读到的数据量
- 若出错,则直接退出simple_cat
- n_read不为0,即还有内容需要输出到屏幕上,则使用full_write函数进行输出,full_write函数的返回值为已经输出的数量,若数量与读到的数量不符,则die退出。
- 若n_read==0, 即已经读完所有的内容,则正常返回。
static bool
simple_cat (char *buf, idx_t bufsize)
{
/* Loop until the end of the file. */
while (true)
{
/* Read a block of input. */
size_t n_read = safe_read (input_desc, buf, bufsize);
if (n_read == SAFE_READ_ERROR)
{
error (0, errno, "%s", quotef (infile));
return false;
}
/* End of this file? */
if (n_read == 0)
return true;
/* Write this block out. */
if (full_write (STDOUT_FILENO, buf, n_read) != n_read)
die (EXIT_FAILURE, errno, _("write error"));
}
}
6.safe_read函数和full_write(简要介绍)
普通的read函数在读第一个字符之前可能被signal中断,而safe_read函数能够恢复被中断的read过程,普通的write函数在读写的过程中也可能会被signal中断,而full_write能够恢复读写过程,直到读写到了指定数目的字节或者到达文件的结尾(EOF),或者就是读写出现错误返回。两个函数在读写不发生错误的情况下返回的为写的字节的数量,错误情况下返回值为ERROR类的值。
故可以使用safe_read函数和full_write函数来进行读写,并且来判断所读的内容大小与所写的内容大小是否相同, 以此来防止读写出错。
size_t /* 原始的read()函数返回值是 ssize_t */
safe_rw (int fd, void const *buf, size_t count)
{
/* Work around a bug in Tru64 5.1. Attempting to read more than
INT_MAX bytes fails with errno == EINVAL. See
<http://lists.gnu.org/archive/html/bug-gnu-utils/2002-04/msg00010.html>.
When decreasing COUNT, keep it block-aligned. */
enum { BUGGY_READ_MAXIMUM = INT_MAX & ~8191 };
for (;;)
{
ssize_t result = rw (fd, buf, count);
if (0 <= result)
return result;
else if (IS_EINTR (errno))/* signal 中断 */
continue;
else if (errno == EINVAL && BUGGY_READ_MAXIMUM < count)
count = BUGGY_READ_MAXIMUM;
else /* 返回 (size_t) -1 */
return result;
}
}
size_t
full_rw (int fd, const void *buf, size_t count)
{
size_t total = 0;
const char *ptr = (const char *) buf;
while (count > 0)
{
size_t n_rw = safe_rw (fd, ptr, count);
if (n_rw == (size_t) -1)<span style="white-space:pre"> </span>/* error */
break;
if (n_rw == 0)<span style="white-space:pre"> </span>/* reach EOF */
{
errno = ZERO_BYTE_TRANSFER_ERRNO;
break;
}
total += n_rw;
ptr += n_rw;
count -= n_rw;
}
return total;
}