命令行参数的误解
前言
我们都知道C语言
中允许main函数
拥有0
个或2
个参数,但也存在部分操作系统向程序传入更多的参数
,还有部分实现中对标准进行扩展,允许main函数
拥有更多的参数
。
命令行参数
作为main函数
的两个参数被传递给程序,这两个参数通常被命名为int argc,char **argv
,其中argc
为参数的数量,argv
为一个指向内含 argc + 1
个 char 类型指针
的指针数组
。
但仅用这段话进行描述可能难以对命令行参数有一个正确的认识,这种描述可能对命令行参数的理解不利。
我们先来分析一个程序。
/* ShowCommandLineArgument.c */
#include <stdio.h>
int main(int argc, char **argv)
{
printf("argc:%d\n", argc);
for (int i = 0; i < argc; i++)
printf("argv[%d]:%s\n", i, argv[i]);
//argv[i]就是*(argv+i),很明显是一个指向char的指针
//程序并不以%s打印argv[argc],而是退出循环。
//请不要忘记:表达式(argv[argc]==NULL)为真
printf("argv[%d]:%p\n", argc, argv[argc]);
return 0;
}
在笔者的电脑中,该文件被存储在/home/admin/blog/ShowCommandLineArgument.c
,输入命令 gcc ShowCommandLineArgument.c
进行编译,得到a.out
,并以cd && ./blog/a.out -f ~/bolg/test1.md >./blog/test2.md /home/admin/blog/test3.md ./blog/test4.md
执行该程序。
请思考,该程序会输出什么内容?你是否认为程序的输出为
argc:6
argv[0]:a.out
argv[1]:-f
argv[2]:~/bolg/test1.md
argv[3]:>./blog/test2.md
argv[4]:/home/admin/blog/test3.md
argv[5]:./blog/test4.md
argv[6]:(nil)
什么?你说没看到输出?请认真查看笔者输入的指令,其中包括了 >./blog/test2.md
意味把 a.out
的 标准输出
重定向
至文件>./blog/test2.md
。所以笔者使用 cat >./blog/test2.md
查看输出的内容,该程序在笔者的设备上的输出为:
argc:5
argv[0]:./blog/a.out
argv[1]:-f
argv[2]:/home/admin/bolg/test1.md
argv[3]:/home/admin/blog/test3.md
argv[4]:./blog/test4.md
argv[5]:(nil)
是不是和你的预期不尽相同,请听笔者逐一解释。
常见误区
误区1—“认为 argv[0] 存储文件名”
实际上,argv[0]
会存储调用的指令中的第一个字符串,而不是文件名,strcmp(argv[0],__FILE__)
并不总为0
。
误区2—“认为命令行参数总是被原样传递”
在上面的例子中可以发现,相对路径
~/blog/test1.md
作为命令行参数
传给程序,程序收到的实际上是文件的绝对路径
/home/admin/blog/test3.md
。
但同为相对路径
的./blog/a.out
和./blog/test4.md
却可以正常传递给程序,而不被转换为绝对路径
。
其他的相对路径写法是否能被正常传递?笔者在此使用由 ShowCommandLineArgument.c
编译得到的 a.out
文件继续测试。使用的指令为 ~/blog/a.out ./test/../blog/test1.md ../test2.md ~admin/blog/test3.md
由这两次测试,笔者大胆猜测只有以 ~
开头的相对路径
会被转换为绝对路径
然后才传递给程序。
argc:4
argv[0]:/home/admin/blog/a.out
argv[1]:./test/../blog/test1.md
argv[2]:../test2.md
argv[3]:/home/admin/blog/test3.md
argv[4]:(nil)
为什么要这么做呢?
请分析笔者的这个程序。
#include <stdio.h>
int main(void)
{
char path1[] = "./blog/test2.md";
char path2[] = "~/blog/test2.md";
char path3[] = "~admin/blog/test2.md";
char path4[] = "~/blog/test2.md";
char path5[] = "../blog/test2.md";
if (fopen(path1, "r") == NULL)
perror("path1");
else
printf("1Success\n");
if (fopen(path2, "r") == NULL)
perror("path2");
else
printf("2Success\n");
if (fopen(path3, "r") == NULL)
perror("path3");
else
printf("3Success\n");
if (fopen(path4, "r") == NULL)
perror("path4");
else
printf("4Success\n");
if (fopen(path5, "r") == NULL)
perror("path5");
else
printf("5Success\n");
return 0;
}
笔者用cd && ./blog/a.out
调用该程序编译得到的可执行文件,得到的输出为:
1Success
path2: No such file or directory
path3: No such file or directory
path4: No such file or directory
path5: No such file or directory
我们可以惊讶的发现只有第一次成功的打开了文件,其他4次操作全部报错。当然,其中第五次打开文件的操作失败是理所当然的,因为确实没有这个文件存在。笔者复制该可执行文件至~/test/a.out
后重新执行该程序即发现,第5次文件打开操作成功了。
path1: No such file or directory
path2: No such file or directory
path3: No such file or directory
path4: No such file or directory
5Success
这说明:fopen()
无法识别以~
开头的相对路径
,也体现了命令行参数在传递过程中,转换以~
开头的相对路径
为绝对路径
的必要性。
误区3–“认为重定向是命令行参数”
重定向虽然也在命令行参数
的位置,但和命令行参数
具有本质的区别。
实践说明重定向指令不会被当中命令行参数传递给程序。
在开发中应该小心,防止误认,也需防止命令行参数中出现相关符号被系统当做重定向指令,导致命令行参数传递错误。
测试环境
OS: Arch Linux
Kernel: x86_64 Linux 5.8.14-arch1-1
参考书籍
-
[1] Stephen Prata.C Primer Plus[M].第六版.姜佑,译.北京:人民邮电出版社
-
[2] Kenneth.A.Reek.C和指针[M].徐波,译.北京:人民邮电出版社.2008
This work is licensed under a Creative Commons Attribution 4.0 International License.