在 Linux 中更好地使用C/C++语言

一、Linux下的命令行处理

C 语言中命令行参数

执行程序时,可以从命令行传入参数给 C 的 main 函数。这些参数被称为命令行参数,它们对程序很重要,可以从外部控制程序的执行。

使用 main() 的函数参数可以处理命令行参数:

  • argc 是指传入参数的个数,包括最开头的执行程序名
  • argv[] 是一个指针数组,指向传递给程序的每个参数。

举例来说:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(int argc, char *argv[]) {
if (argc == 2)
printf("The argument supplied is %s\n", argv[1]);
else if (argc > 2)
printf("Too many arguments supplied.\n");
else
printf("One argument expected.\n");
}

现在编译上面的程序,并执行 > ./a.out testing,它会产生下列结果:

1
2
$./a.out testing
The argument supplied is testing

getopt

但上面的机制还是过于简陋了。通常,命令行参数还带有很多选项(option),提供用户选择执行程序不同功能的机会,如查询版本一般用 -V 或者 --version 等,查看帮助文档一般使用 --helo-h 等。为了方便处理,Linux 提供了更强大的函数,帮助开发者更好更快地解析命令行传来的参数。

先介绍 getopt() 函数:

1
2
3
4
5
6
#include <unistd.h>

int getopt(int argc, char *const argv[], const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

getopt() 函数的前两个参数之前已经介绍过了,第三个参数 optstring 是一个字符列表,其中的每个字母代表一个选项。

举例说明,optstring 的具体用法:当我们将 optstring = "ab:c::" 时,它表示:

  • 单个字符 a: 表示选项a 没有参数,即在命令行输入 -a 即算打开该功能,类似于其他执行程序中查看帮助的 -h
  • 单个字符加冒号 b: 表示选项b 在命令行输入时必须有参数,如 -b 10
  • 单个字符加两个冒号 c:: 表示选项c 在命令行输入时 -c 后跟的参数是可有可无的

getopt() 函数的返回值很有意思:

  • 如果处理的 option 成功,那么返回选项的字母,如果有值跟随,那么字符串会被放在 optarg 中。
  • 如果处理的 option 需要一个值,但命令行中没有给定值,返回 :
  • 如果处理了一个未知的 option ,返回 ?,并将值存入到 optopt
  • 如果没有更多的 option 等待处理,返回 -1
  • 如果多余出一些跟随值,那么会将多余的存放在argv数组中,optindargc分别充当索引和总数。

再注意一下全局变量 optind opterr optopt,它们在解析命令行参数时非常重要。

  • extern char *optarg; 存放了正在被处理的 option 后跟的参数
  • extern int optind; 表下一个将被处理到的参数在 argv 中的下标值
  • extern int opterr; 正常运行状态下为 0。非零时表示存在无效选项或者缺少选项参数,并输出其错误信息
  • extern int optopt; 包含的发现未知 option 的无效选项字符

下面,放上一个简单的程序示例,例如要实现一个程序,要求有选项功能如下:> ./a.out -i -f file.txt -lr -x 'hero'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int opt;
// put ':' in the starting of the
// string so that program can
// distinguish between '?' and ':'
while((opt = getopt(argc, argv, ":if:lrx:")) != -1) {
switch(opt) {
case 'i':
case 'l':
case 'r':
printf("option: %c\n", opt);
break;
case 'f':
case 'x':
printf("option: %c argname: %s\n", opt, optarg);
break;
case ':':
printf("option needs a value\n");
break;
case '?':
printf("unknown option: %c\n", optopt);
break;
}
}
// optind is for the extra arguments
// which are not parsed
for(; optind < argc; optind++) {
printf("extra arguments: %s\n", argv[optind]);
}
return 0;
}

编译后运行,在命令行中输入 > ./a.out -i -f file.txt -lr -x hero,则会输出:

1
2
3
4
5
option: i
option: f argname: file.txt
option: l
option: r
option: x argname: hero

而如果输入 > ./a.out -q -x,则会:

1
2
3
unknown option: q
option: x argname: -w
extra arguments: 2

getopt_long

getopt_long() 函数与 getopt_long_only() 函数,它们的工作方式与 getopt() 函数很像,除了这些函数还可以接收(getopt_long_only()除外)长选项--,形式可以为--arg=param或者--arg paramgetopt_long() 函数用法如下:

1
2
3
4
5
6
7
8
9
int getopt_long(int argc, char *const *argv, const char* shortopts, 
const struct option *longopts, int longind);
// 其中结构体option
struct option {
const char *name; // name of ong option
int has_arg; // 0 no 1 required 2 optional
int *flag; // how results returned
int val; // value to return
}

前两个参数相信早已不陌生,重点介绍后三个参数,optstring 是格式控制符,longoptsstruct option 结构体组成的数组,该结构体表示的是“长参数”(即形如-–name 的参数)名称和性质:

  • name 长选项名称
  • has_arg 参数可选项,no_argument 表示该选项后不带参,required_argument 表示该选项后面带参数
  • *flag 匹配到选项后,如果flag是 NULL ,则返回val;如果不是 NULL ,则返回0,并且将 val 的值赋给flag指向的内存
  • val 匹配到选项后的返回值

longindex,表示是当前处理 longopts 数组的下标值。

下面也给出个简单的例子,方便大家理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

static int verbose_flag;

int main (int argc, char **argv) {
int c;
while (1) {
static struct option long_options[] = {
/* 下面的选项会将返回值写入 verbose_flag */
{"verbose", no_argument, &verbose_flag, 1},
{"brief", no_argument, &verbose_flag, 0},
/* 下面的选项返回值是val值 no_argument required_argument是宏常量 */
{"add", no_argument, 0, 'a'},
{"beyond", no_argument, 0, 'b'},
{"delete", required_argument, 0, 'd'},
{"create", required_argument, 0, 'c'},
{"file", required_argument, 0, 'f'}
};

/* getopt_long 数组索引 */
int option_index = 0;
c = getopt_long (argc, argv, "abc:d:f:",
long_options, &option_index);
/* 返回-1 循环结束 */
if (c == -1)
break;
switch (c) {
case 0:
/* If this option set a flag, do nothing else now. */
if (long_options[option_index].flag != 0)
break;
printf ("option %s", long_options[option_index].name);
if (optarg)
printf (" with arg %s", optarg);
printf ("\n");
break;
case 'a':
printf ("option -a\n");
break;
case 'b':
printf ("option -b\n");
break;
case 'c':
printf ("option -c with value `%s'\n", optarg);
break;
case 'd':
printf ("option -d with value `%s'\n", optarg);
break;
case 'f':
printf ("option -f with value `%s'\n", optarg);
break;
case '?':
/* getopt_long already printed an error message. */
break;
default:
abort();
}
}
printf ("verbose flag is %d\n", verbose_flag);

/* 输出多余的命令行参数 */
if (optind < argc) {
printf ("non-option ARGV-elements: ");
while (optind < argc)
printf ("%s ", argv[optind++]);
putchar ('\n');
}
return 0;
}

编译后执行,可以看下图结果:

二、Linux下的时间处理

Linux内核提供的基本时间服务是计算自协调世界时(UTC)公元1970年1月1日 00:00:00 这一特定时间以来经过的秒数。这种秒数用time_t数据结构表示。

time()函数返回当前的秒数。

1
2
3
#include <time.h>

time_t time(time_t *calptr);

时间值作为函数值返回。若参数非空,时间值也会放在calptr指向的值中。

clock_gettime()函数可以用于获取指定时钟的时间,返回的时间在timespec数据结构中,它将时间分为秒和纳秒。clock_id用于指示选项,常用的有CLOCK_REALTIMECLOCK_MONOTONICCLOCK_PROCESS_CPUTIME_ID

1
2
3
4
5
#include <sys/time.h>

int clock_gettime(clockid_t clock_id, struct timespec *tsp);
int clock_getres(clockid_t clock_id, struct timespec *tsp);
int clock_settime(clockid_t clock_id, struct timespec *tsp);

clock_getres函数把tsp指向的timespec结构初始化为clock_id参数对于的时钟精度。我们还可以使用clock_settime函数设置时间,但有些时钟不能修改。

以上这些函数得到的数字都是自UTC时间的秒数,这对人类非常不友好。需要用localtimegmtimestrftime等函数将秒数转为可读时间。localtimegmtime将时间转换存入到结构体tm中。而mktime函数将tm时间转换为秒数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <time.h>
struct tm {
int tm_sec; // [0-60] 允许润秒
int tm_min; // [0-59]
int tm_hour; // [0-23]
int tm_mday; // [1-31]
int tm_mon; // [0-11]
int tm_year; // years since 1900
int tm_wday; // [0-6]
int tm_yday; // [0-365]
int tm_isdst; // daylight saving time flag
}

struct tm *gmtime(const time_t *calptr);
struct tm *localtime(const time_t *calptr);

time_t mktime(struct tm *tmptr);

当然,gmtimelocaltime函数仍然不能满足人们的需要。函数strftime是类似于printf的时间值函数,可以通过多个参数定制产生的字符串。

1
2
3
4
5
6
7
#include <time.h>

size_t strftime(char *buf, size_t maxsize, const char *format,
const struct tm *tmptr);
size_t strftime_l(char *buf, size_t maxsize, const char *format,
const struct tm *tmptr, locale_t locale);
char *strptime(const char *buf, const char *format, struct tm *tmptr);

tmptr是要格式化的时间值,格式化的结果存放在长度为maxsizebuf数组中,如果长度不足,函数返回0,否则返回在 buf 中存放的字符数。format是控制时间值的格式,与printf相同。

这是使用说明:

strptimestrftime的反过来的版本,把字符串时间转换为分解时间。

这些函数的转换关系,可以用这一张图概括:


在 Linux 中更好地使用C/C++语言
https://dingfen.github.io/2021/08/25/2021-8-25-tricks-in-LinuxC/
作者
Bill Ding
发布于
2021年8月25日
更新于
2024年4月9日
许可协议