linux常用命令 coreutils软件包cat命令详解并了解getopt_long解析长命令行选项

系列文章目录



前言

当想了解 c/c++ 标准库 <getopt.h> 中getopt_long 解析长命令行选项时,可以不用参考其他网上内容,只需要执行 cat --help 就能发现 cat 命令的使用了该API。那么接下来就是找该命令所在的软件包 coreutils 并查看相关源码使用了。

因此,多了解 linux 常用命令的使用是很有必要的,对以后的项目开发有很大帮助。


一、cat 是什么?

以下是 cat --help 中文手册:

$ cat --help 
用法:cat [选项]... [文件]...
连接一个或多个 <文件> 并输出到标准输出。

如果没有指定 <文件>,或者 <文件>"-",则从标准输入读取。

  -A, --show-all           等价于 -vET
  -b, --number-nonblank    对非空输出行编号,使 -n 失效
  -e                       等价于 -vE
  -E, --show-ends          在每行行末显示 "$"
  -n, --number             对输出的所有行编号
  -s, --squeeze-blank      不重复输出空行
  -t                       等价于 -vT
  -T, --show-tabs          将 TAB 显示为 ^I
  -u                       (被忽略)
  -v, --show-nonprinting   使用 ^ 和 M- 表示法,LFD 和 TAB 字符除外
      --help        显示此帮助信息并退出
      --version     显示版本信息并退出

示例:
  cat f - g  先输出 f 的内容,然后输出标准输入的内容,最后输出 g 的内容。
  cat        将标准输入的内容复制到标准输出。

GNU coreutils 在线帮助:<https://www.gnu.org/software/coreutils/>
请向 <http://translationproject.org/team/zh_CN.html> 报告任何翻译错误
完整文档 <https://www.gnu.org/software/coreutils/cat>
或者在本地使用:info '(coreutils) cat invocation'

由此可知,cat 命令所在软件包为 coreutils 。也可通过 whereis cat 并执行 dpkg -S 进行查找软件包名。

在软件包 coreutils 中发现 cat 命令入口文件是 src/cat.c

coreutils-9.1$ find . -name "*cat*"
./man/cat.x
./man/truncate.1
./man/truncate.x
./man/cat.1
./m4/filenamecat.m4
./m4/strncat.m4
./m4/ftruncate.m4
./lib/filenamecat.h
./lib/allocator.c
./lib/ftruncate.c
./lib/allocator.h
./lib/strncat.c
./lib/filenamecat.c
./lib/filenamecat-lgpl.c
./src/truncate.c
./src/cat.c
./tests/misc/truncate-relative.sh
./tests/misc/truncate-fifo.sh
./tests/misc/cat-buf.sh
./tests/misc/truncate-parameters.sh
./tests/misc/cat-E.sh
./tests/misc/truncate-owned-by-other.sh
./tests/misc/cat-self.sh
./tests/misc/truncate-fail-diag.sh
./tests/misc/truncate-overflow.sh
./tests/misc/truncate-dangling-symlink.sh
./tests/misc/cat-proc.sh
./tests/misc/truncate-no-create-missing.sh
./tests/misc/truncate-dir-fail.sh
./tests/df/skip-duplicates.sh
./tests/dd/no-allocate.sh
./tests/tail-2/truncate.sh
./gnulib-tests/unistr/test-strncat.h
./gnulib-tests/test-filenamecat.c
./gnulib-tests/test-strncat.c
./gnulib-tests/test-ftruncate.sh
./gnulib-tests/test-ftruncate.c

二、获取getopt_long解析长命令行选项

可以根据 coreutils/src/cat.c 获取getopt_long解析长命令行选项使用方法:

int
main (int argc, char **argv)
{
  /* Nonzero if we have ever read standard input.  */
  bool have_read_stdin = false;

  struct stat stat_buf;

  /* Variables that are set according to the specified options.  */
  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;

  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;

        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);

        default:
          usage (EXIT_FAILURE);
        }
    }

  /* Get device, i-node number, and optimal blocksize of output.  */

  if (fstat (STDOUT_FILENO, &stat_buf) < 0)
    die (EXIT_FAILURE, errno, _("standard output"));

  /* Optimal size of i/o operations of output.  */
  idx_t outsize = io_blksize (stat_buf);

  /* Device and I-node number of the output.  */
  dev_t out_dev = stat_buf.st_dev;
  ino_t out_ino = stat_buf.st_ino;

  /* True if the output is a regular file.  */
  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);
    }

  /* Main loop.  */

  infile = "-";
  int argind = optind;
  bool ok = true;
  idx_t page_size = getpagesize ();

  do
    {
      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;
        }

      /* Pointer to the input buffer.  */
      char *inbuf;

      /* Select which version of 'cat' to use.  If any format-oriented
         options were given use 'cat'; if not, use 'copy_cat' if it
         works, 'simple_cat' otherwise.  */

      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
        {
          /* Allocate, with an extra byte for a newline sentinel.  */
          inbuf = xalignalloc (page_size, insize + 1);

          idx_t bufsize;
          if (INT_MULTIPLY_WRAPV (insize, 4, &bufsize)
              || INT_ADD_WRAPV (bufsize, outsize, &bufsize)
              || INT_ADD_WRAPV (bufsize, LINE_COUNTER_BUF_LEN - 1, &bufsize))
            xalloc_die ();
          char *outbuf = xalignalloc (page_size, bufsize);

          ok &= cat (inbuf, insize, outbuf, outsize, show_nonprinting,
                     show_tabs, number, number_nonblank, show_ends,
                     squeeze_blank);

          alignfree (outbuf);
        }

      alignfree (inbuf);

    contin:
      if (!reading_stdin && close (input_desc) < 0)
        {
          error (0, errno, "%s", quotef (infile));
          ok = false;
        }
    }
  while (++argind < argc);

  if (pending_cr)
    {
      if (full_write (STDOUT_FILENO, "\r", 1) != 1)
        die (EXIT_FAILURE, errno, _("write error"));
    }

  if (have_read_stdin && close (STDIN_FILENO) < 0)
    die (EXIT_FAILURE, errno, _("closing standard input"));

  return ok ? EXIT_SUCCESS : EXIT_FAILURE;
}

三、getopt_long 函数使用

getopt_long函数包含了getopt函数的功能,并且还可以指定“长参数”(或者说长选项),与getopt函数对比,getopt_long比其多了两个参数。

struct option 说明如下:

struct option
{
  const char *name; // 长选项名
  /* has_arg can't be an enum because some compilers complain about
     type mismatches in all the code that assumes it is an int.  */
  /* 是否带参数或可选参数:
  	no_argument : 这个长参数不带参数(即不带数值,如:--name)
  	required_argument : 这个长参数必须带参数(即必须带数值,如:--name Tom)
  	optional_argument : 这个长参数后面带的参数是可选的,(即--name和--name Tom 都可以) */
  int has_arg; 
  int *flag; // 确定函数返回值的情况,如果flag==NULL,则识别选项后返回val
  int val; // 设置为返回值
};

注:
longopts的最后一个元素必须是全0填充,否则会报段错误。

以下是

#define _GNU_SOURCE
 
#include <getopt.h>
 
extern char *optarg;
 
extern int optind, opterr, optopt;
 
int getopt_long(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex);

注:

  • 相比getopt,使用getopt_long需要加头文件<getopt.h>;
  • getopt_long除了会接受长选项,其他概念和getopt是一样的;
  • 如果使用getopt_long想只接受短选项,设置longopts为NULL即可;如果只想接受长选项,相应地设置optstring为NULL即可;
  • 长选项名是可以使用缩写方式,比如:选项有–show-all ,那么输入 --s 等均会被正确识别为show-all选项;
  • 对于带参数的长选项格式是:–arg=param或–arg param;
  • longopts是指向struct option数组的第一个元素的指针,struct option定义在<getopt.h>中;
  • longindex如果非NULL,则是返回识别到struct option数组中元素的位置指针;
  • getopt_long仅仅只能将"–“开始的选项视为长选项,但getopt_long_only将”-“开头选项也会视为长选项,当长选项列表均不满足时,且短选项满足时,”-"才会解析为短选项;

四、 getopt_long 简单案例

以下是 getopt_long 简单案例:

#include <stdio.h>
#include <getopt.h>
int main(int argc, char **argv)
{
    int option, index;
    static struct option long_options[] = {
        {"help", no_argument, NULL, 'h'},
        {"option", required_argument, NULL, 'o'},
        {0, 0, 0, 0}};

    opterr = 0;
    while ((option = getopt_long(argc, argv,
                                 "ho:", long_options, NULL)) != -1)
    {
        switch (option)
        {
        case 'h': /* help */
            printf("usage: %s [-h] [-o arg]\n", argv[0]);
            break;
        case 'o': /* option */
            printf("optarg is %s\n", optarg);
            break;
        default: /* ? */
            printf("option not recognized: %c\n", optopt);
        }
    }
    for (index = optind; index < argc; index++)
    {
        puts(argv[index]);
    }
    return 0;
}

以下是编译结果:

$ ./a.out  --help 
usage: ./a.out [-h] [-o arg]

$ ./a.out  -o  show-all
optarg is show-all

$ ./a.out  -?
option not recognized: ?

总结

本文讲解了cat 命令以及getopt_long解析长命令行选项的使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yusq77

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值