cat源代码(部分注解)

当我们在命令行直接输入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 的函数

  1. 若有参数则选择cat函数
  2. 若无参数则先进行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来记录已经读到的数据量

  1. 若出错,则直接退出simple_cat
  2. n_read不为0,即还有内容需要输出到屏幕上,则使用full_write函数进行输出,full_write函数的返回值为已经输出的数量,若数量与读到的数量不符,则die退出。
  3. 若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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值