系列文章目录
前言
当想了解 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解析长命令行选项的使用。