命令行参数的误解

命令行参数的误解

前言

我们都知道C语言中允许main函数拥有0个或2个参数,但也存在部分操作系统向程序传入更多的参数,还有部分实现中对标准进行扩展,允许main函数拥有更多的参数
命令行参数作为main函数的两个参数被传递给程序,这两个参数通常被命名为int argc,char **argv,其中argc为参数的数量,argv为一个指向内含 argc + 1char 类型指针指针数组

但仅用这段话进行描述可能难以对命令行参数有一个正确的认识,这种描述可能对命令行参数的理解不利。
我们先来分析一个程序。

/* 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


Creative Commons License

This work is licensed under a Creative Commons Attribution 4.0 International License.

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值