Linux 下命令行参数和环境变量

命令行参数

我们平时写 C 语言代码时, main() 函数是必须要写的;虽然我们一般并不会给 main() 函数带上参数,但 main() 函数的确是 有参数的

int main(int argc, char* argv[])
{
	return 0;
}

先来解释:
argv 是个 指针数组 ,它本质上是一个 数组 ,里面都是 char* 类型的指针
argc 是指 argv元素的个数

咱们先来做一次测试,看看 argv 里到底有什么 :

// test.c 文件
#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    for (int i = 0; i < argc; ++i)
    {
        printf("argv[%d] -> %s\n", i, argv[i]);
    }
    return 0;
}

编译为 mytest 可执行文件,在 命令行 上运行如下指令:

./mytest
./mytest -a
./mytest -a -b
./mytest -a -b -c
./mytest -a -b -c -d

结果如下:

[root@localhost command_line_parameter]$ ./mytest
argv[0] -> ./mytest
[root@localhost command_line_parameter]$ ./mytest -a
argv[0] -> ./mytest
argv[1] -> -a
[root@localhost command_line_parameter]$ ./mytest -a -b
argv[0] -> ./mytest
argv[1] -> -a
argv[2] -> -b
[root@localhost command_line_parameter]$ ./mytest -a -b -c
argv[0] -> ./mytest
argv[1] -> -a
argv[2] -> -b
argv[3] -> -c

一目了然 argv 里面是什么,显然是将输入的指令字符串以空格为界限切开,分别存进 argv 元素所指的空间里,但不论有几个元素, argv 结尾的元素都是 null

切割原理也很简单,无非就是将 空格 替换为 '\0' 即可,如此一个长字符串就变为多个子串

那这有什么意思呢?
请用上面的方法验证下面的代码:

// test.c 文件
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        printf("Usage: %s -[a, b, c, d]\n", argv[0]);
        return 1;
    }

    if (strcmp(argv[1], "-a") == 0)
        printf("It is function1\n");
    else if (strcmp(argv[1], "-b") == 0)
        printf("It is function2\n");
    else if (strcmp(argv[1], "-c") == 0)
        printf("It is function3\n");
    else if (strcmp(argv[1], "-d") == 0)
        printf("It is function4\n");
    else 
        printf("Nothing!!!\n");
    return 0;
}

验证结果是否感觉有些似曾相识?没错,就是指令后带选项罢了,类似 ls -l 等等

为什么要有命令行参数

命令行参数本质是 交给程序不同的选项,从而定制出不同的功能;就像是 Linux 的指令携带有自己的选项一样

目前遇到的许多命令,都是 C 语言写的

谁可以做到

验证下面的代码就得知:父进程的数据,默认能被子进程看到并访问

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int g_val = 100;

int main()
{
    printf("It is a process, pid: %d, ppid: %d, g_val: %d\n", getpid(), getppid(), g_val);
    sleep(5);

    pid_t id = fork();
    if (id == 0)
    {
        // child
        while (1)
        {
            printf("It is a child process, pid: %d, ppid: %d, g_val: %d\n", getpid(), getppid(), g_val);
            sleep(1);
        }
    }
    else 
    {
        // parent
        while (1)
        {
            printf("It is a parent process, pid: %d, ppid: %d, g_val: %d\n", getpid(), getppid(), g_val);
            sleep(1);
        }
    }
    return 0;
}

此时我们观察第一行打印数据,其父进程,查询后会发现就是 bash 进程,也就是说, bash 进程是所有在命令行下启动的进程的父进程

所以我们平时输入的指令字符串是默认交给父进程 bash 的(命令行解释器)

所以 argv 里头的指针是 bash 的功劳啦,由于 子进程可以访问父进程的数据,那也就可以将我们输入的指令字符串切割后传入 argv 里让子进程访问到

结论

命令行参数是 C 语言支持让我们给 main() 函数传参的方式,支持我们写可变选项的程序,使同一个程序定制出不同的功能,就类似 Linux 里带选项的指令(百分之七十的指令都是 C 语言实现)

环境变量

环境变量(environment variables)一般是指在 OS 中用来指定 OS 运行环境的一些参数,如:我们在编写 C/C++ 代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关 环境变量 帮助编译器进行查找

一些现象

通过上面的实验,虽然已经可以为我们的程序带上选项,但 Linux 系统指令似乎并不用带上路径,类似 pwdls 等等,而我们的程序却要带上路径才能运行,成为进程,这是为什么呢?

其实 pwd 也有路径,不信的话,下面两句指令是一样的:

/usr/bin/pwd
pwd

指令可以不带路径是因为系统可以找到它呀 ^ ^

在 Linux 当中,存在一些 全局的设置,告诉命令行解释器,应该去哪些路径下去寻找可执行程序

而这个全局的设置是指: PATH ( Linux 环境变量的一个)

指定命令的搜索路径

查看环境变量

echo $PATH

结果就是:

/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/exercise/.local/bin:/home/exercise/bin

系统中有很多配置在我们登录 Linux 系统的时候,就已经被加载到了 bash 进程中(内存),其中就包括 PATH

bash 在执行命令的时候,需要先找到命令,因为是要提前加载的 ,所以 bash 内部维护了一批路径在环境变量里,而这些路径以 : 作为分隔符;
要执行哪个指令就会依次在这些路径下寻找,如果没找到,就会报 command not found 错误;如果找到了,就会加载并运行此程序

而上面 pwd 指令路径就在 /usr/bin/ 下,在 PATH 是存在的,所以不需要手动添加路径

注意:
上面查到的 PATH 默认是 内存级的环境变量,也就是说,一旦我们将 PATH 修改,而 系统配置文件不变,下一次登录时, PATH 值会恢复至原样

添加环境变量

添加内存级环境变量

如果我想自己写的程序也可以像指令一样直接被运行,就需要将此程序的路径添加至环境变量,使得 bash 可以找得到此程序,仔细阅读以下命令

[exercise@localhost envir_var]$ ./mytest
Usage: ./mytest - [a, b, c, d]
[exercise@localhost envir_var]$ ./mytest -a
It is function1
[exercise@localhost envir_var]$ ./mytest -b
It is function2
[exercise@localhost envir_var]$ pwd
/home/exercise/learn/linux-exer/process/envir_var
[exercise@localhost envir_var]$ PATH=$PATH:/home/exercise/learn/linux-exer/process/envir_var
[exercise@localhost envir_var]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/exercise/.local/bin:/home/exercise/bin:/home/exercise/learn/linux-exer/process/envir_var
[exercise@localhost envir_var]$ mytest
Usage: mytest - [a, b, c, d]
[exercise@localhost envir_var]$ mytest -a
It is function1
[exercise@localhost envir_var]$ mytest -b
It is function2
[exercise@localhost envir_var]$

发现此时的 mytest 可执行程序已经可以不带路径直接跑咯 ^ ^

也可以使用 which 指令查看验证哦:

which mytest

所以 添加内存级环境变量 为:

PATH=$PATH:你要添加的路径

永久有效

如果你重新打开终端或者重启系统之后,会发现上面添加的环境变量仅仅在当前 bash 有效,这是因为上面添加的是内存级的环境变量嘛,那如何实现永久有效呢?

那就要修改配置文件咯,对于云服务器来说,每次重新登录时,都会为我们创建一个新的 bash 进程,此 bash 进程会将配置文件数据拷贝一份到自己的内存中,其中 就包括 PATH

如何更改呢?在你的家目录下有一个隐藏文件叫做 .bash_profile ,使用 vim 打开就会发现如下内容:

# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
	. ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/.local/bin:$HOME/bin

export PATH

很显然,我们看见了环境变量 PATH ,所以只需要需改此配置文件即可,在 PATH=$PATH:$HOME/.local/bin:$HOME/bin 的后面添加 :你要添加的路径 即可,我这里是

PATH=$PATH:$HOME/.local/bin:$HOME/bin:/home/exercise/learn/linux-exer/process/envir_var

那么步骤如下:

[exercise@localhost envir_var]$ ./mytest
Usage: ./mytest - [a, b, c, d]
[exercise@localhost envir_var]$ ./mytest -a
It is function1
[exercise@localhost envir_var]$ ./mytest -b
It is function2
[exercise@localhost envir_var]$ pwd
/home/exercise/learn/linux-exer/process/envir_var
[exercise@localhost envir_var]$ cd ~
[exercise@localhost ~]$ vim .bash_profile

然后重启一个终端即可

其他环境变量

在 Linux 里可不止 PATH 一种环境变量,如果你要查看系统中所有的环境变量,使用如下指令:

env

结果会非常多,琳琅满目,暂挑几个说:

HOME

指定用户的主工作目录(即用户登陆到 Linux 系统中时,默认的目录)

如果你是 普通用户,那么登录进入 Linux 系统后,所在位置一般都是 用户家目录

[exercise@localhost ~]$ pwd
/home/exercise

如果你是 超管 root ,那你的家目录就在 /root 下:

[root@localhost ~]# pwd
/root

为什么初次登录系统的目录是这个呢?当然是 系统的配置文件决定的 啦,它会为用户配置好系统家目录

[exercise@localhost envir_var]$ echo $HOME
/home/exercise

PWD

这是个有趣的环境变量:

[exercise@localhost envir_var]$ echo $PWD
/home/exercise/learn/linux-exer/process/envir_var
[exercise@localhost envir_var]$ cd ..
[exercise@localhost process]$ echo $PWD
/home/exercise/learn/linux-exer/process

会发现这就是我们当前的工作目录,这就是 pwd 指令为啥知道我们的工作目录呢?

这是因为系统有一个会变化的环境变量,会随着我们路径的变化,动态地将我们的路径记录在此环境变量里

SHELL

当前 Shell,它的值通常是 /bin/bash

机器启动时,会为用户创建一个命令行解释器 shell 提供服务,那 Linux 怎么知道要运行什么 shell 呢?

[exercise@localhost envir_var]$ echo $SHELL
/bin/bash

HISTSIZE

不知道大家是否会喜欢使用上键来翻找被执行过的历史指令,我甚至可以使用 ctrl + r 来搜索被执行过的历史指令,这些就说明我们使用过的历史指令是可以被系统记录下来的,但 Linux 系统总不能一直记录你所使用过的历史指令呀,所以 HISTSIZE 环境变量就表示 可以被记录的最新的指令条数

[exercise@localhost envir_var]$ echo $HISTSIZE
10000

也即是说系统会为我们维护最新的 10000 条指令,我们也可以使用 history 指令来查看被执行过的历史指令

自定义环境变量

定义

格式:

export name=val

例子:

export MY_ENV=HelloWorld

查看:

[exercise@localhost envir_var]$ env | grep MY_ENV
MY_ENV=HelloWorld
[exercise@localhost envir_var]$ echo $MY_ENV
HelloWorld

同样这也是 内存级的环境变量

取消

格式:

unset name 

例子:

unset MY_ENV

本地变量

就是不带 export 定义一个变量,观察以下操作:

[exercise@localhost envir_var]$ MY_VAR=123456
[exercise@localhost envir_var]$ env | grep MY_VAR
[exercise@localhost envir_var]$ echo $MY_VAR
123456
[exercise@localhost envir_var]$

会发现在 环境变量 里查不到 MY_VAR ,但可以 echo 出来,这就是 本地变量

整体理解环境变量

系统会把与登录相关的,与用户相关的,与路径相关的,与程序相关的等等,所有这些周边的,在系统中设置好的全局的变量就叫做环境变量

查看 所有环境变量 就是:

env

查看单个环境变量 XXX 就是:

echo $XXX

环境变量的组织方式

bash 内部会维护一张 环境变量表,本质就是个 字符指针数组,其内元素指向一个以 '\0' 结尾的字符串,最后一个元素指向 NULL
在这里插入图片描述

Linux 代码获取环境变量

方式一
#include <stdio.h>
#include <unistd.h>

int main()
{
	// 其他头文件里的全局变量 environ(指针数组),需要 extern 声明
    extern char** environ;
    for (int i = 0; environ[i]; ++i)
        printf("env[%d] -> %s\n", i, environ[i]);
    return 0;
}

编译运行后观察结果,和 env 指令结果本质上是一样的,只是格式不一样

环境变量默认是可以被子进程拿到的,而环境变量默认是在 bash 内部,上面的程序运行为进程后会从 父进程 bash 里看到全局的环境变量并读取

bash 进程启动的时候,默认会给我们子进程形成两张表:

  • 命令行参数表 argv[] (从用户输入的命令行来)
  • 环境变量表 env[] (从 OS 配置文件来)

bash 会通过各种方式交给子进程,而把环境变量给子进程的一种方式就是此 二级指针

extern char** environ;

environ 没有包含在任何头文件中,所以在使用时 要用 extern 声明
其最后的末尾元素是 NULL 指针,而前面的元素内容也已被上面的代码所打印出来

方式二

这种方式和命令行参数很像,就是 main() 函数接收参数啦:

int main(int argc, char* argv[], char* env[])
{
	return 0;
}

也就可以像下面这样获取:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[], char* env[])
{
    for (int i = 0; env[i]; ++i)
        printf("env[%d] -> %s\n", i, env[i]);
    return 0;
}
方式三

子进程也可以通过 getenv() 函数获取指定环境变量:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char* argv[], char* env[])
{
    char* path = getenv("PATH");
    if (path == NULL) return 1;
    else printf("PATH = %s\n", path);
    return 0;
}

理解

环境变量具有系统级的全局属性,因为环境变量本身会被子进程继承下去

如果你在命令行启动一个进程 A ,那它的父进程是 bash ,自然可以继承 bash 的环境变量;但如果进程A 也启动一个子进程,此时也可以继承 A 的环境变量,如此继承下去

但有个地方不对劲,就是利用 export 指令自定义环境变量:
export 是指令吧?执行它不是要创建子进程吗?如果创建子进程,那子进程修改环境变量对于 bash 来说是不相干的呀,因为进程是具有独立性的

这是因为 export内建命令,Linux 大部分指令确实需要创建子进程来完成执行,但少部分指令却是由 bash 亲自执行的,再例如 echo 指令,这就是内建命令,暂且可以将其理解为一个函数或者是类的调用即可

也就是说系统对 内建命令 的执行是不需要 PATH 指路的,毕竟都不需要开启新的子进程,不信咱就可以把 PATH 设为空串试一下,试完重新登录即可

测试本地变量和环境变量

我们上面提到过 本地变量,那 本地变量 到底可以用来干啥呢?

看下面的一串指令:

[exercise@localhost envir_var]$ env | grep HELLO
[exercise@localhost envir_var]$ echo $HELLO

[exercise@localhost envir_var]$ HELLO=123436
[exercise@localhost envir_var]$ env | grep HELLO
[exercise@localhost envir_var]$ echo $HELLO
123436
[exercise@localhost envir_var]$ export HELLO
[exercise@localhost envir_var]$ env | grep HELLO
HELLO=123436
[exercise@localhost envir_var]$ echo $HELLO
123436
[exercise@localhost envir_var]$

本地变量 还没有被 export 时,env 是查不到的,但 export 后却可以

而这 本地变量 是只在本 bash 内部有效,无法被子进程继承下去,导成 环境变量 才能被继承

这也可以解释 echo 为什么可以打印出 本地变量,这是因为 echo内建命令,由 bash 亲自执行,都不需要继承,获取自己内部的 本地变量 不是易如反掌吗?
而子进程是无法继承 bash 内部的 本地变量,所以 echo 也绝对不是以 bash 创建子进程的方式完成,而是由 bash 亲自执行才可,相互印证

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值