Linux命令行参数与环境变量

目录

命令行参数与环境变量

命令行参数

环境变量及其相关概念

环境变量的相关操作

环境变量的本质


命令行参数与环境变量

命令行参数

我们在使用一些Linux的一些指令时,会有意或无意的使用一些指令参数,例如:

ls -al
ps -ajx
gcc -o test.c proc -g
……

从本质上讲,Linux是用C语言写的,而Linux下的指令其实就是操作系统内置的一个个可执行程序,即Linux下的指令本质上都是用C语言写的可执行程序。那么这些指令参数的功能是如何实现的呢?我们在当时好像并没有想过。其实,这一切都与main函数的命令行参数有关。

在初学C语言时,我们有时会看到这种带参数main函数的写法:

int main(int argc, char* argv[])

但那时我们并不知道这些参数有什么用,表示什么含义,于是久而久之习惯了main函数不带参数的写法。但其实,argc和argv是当前进程的命令行参数,argc是一个int类型的变量,表示命令行终端传入的参数的个数,argv是一个指针数组,以char*的形式保存每一个命令终端传入的参数。其中,命令行参数的第一个内容默认是当前可执行程序的名称。下面就来证明上述内容:

首先我们有一个可执行程序 myproc,其源代码的main函数内容如下:

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

那么当我们以如下形式执行我们的可执行程序myproc

./myproc -l -x -s

那么对应的输出内容为:

argv[0] = ./myproc
argv[1] = -l
argv[2] = -x
argv[3] = -s

由此可见,我们是可以通过main函数的参数实现将外部参数传到可执行程序内部的。所以,Linux内置的指令参数(如ls -l等)就是通过命令行参数来实现的,即指令参数的本质就是命令行参数,这也就很好的呼应了"Linux下的指令本质上都是用C语言写的可执行程序"这句话了。

需要注意的是,main函数的三个命令行参数所在的位置是不能颠倒的,只能严格以

命令终端参数的个数(int argc) 
命令终端的参数内容(char* argv[]) 
环境变量的内容(char* env[])

的顺序书写。不过虽然命令行参数的顺序有严格规定,但是命令行参数的参数名是可以自定义修改的,不过为了统一性和规范性,一般不会选择修改。而至于main函数的第三个命令行参数是什么,后面的环境变量部分会讲到。

环境变量及其相关概念

环境变量的概念引入

大多数情况下,我们在运行自己编写的可执行程序的时候都要在前面加一个 "./" 以表明可执行程序的位置(不加会出现"command not found"之类的内容),那么我们为什么要加这个 "./" 呢?原因很简单,因为操作系统如果想要运行一个可执行程序就要找到这个可执行程序的位置,因此我们一般使用比较方便的相对地址 ".\"来指明我们可执行程序的位置。

值得一提的是,其实进程的相对地址本质上就是绝对地址,只不过Linux操作系统将当前目录的地址以链接文件的形式将其保存起来了:

详见 "进程标识符 - PID" 部分

可是,Linux内置的指令本质上也是可执行程序,那么为什么执行这些指令的时候不需要指定具体的路径位置呢?这就与一个叫做环境变量的东西有关了。

那么何为环境变量呢?环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。换言之,环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。例如Windows下的path环境变量,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还会到path中指定的路径去寻找。

那么Linux也是如此,我们的Linux内置指令确实是一个个的可执行程序,但至于为什么可以不用手动指定其路径位置,是因为Linux中有一个PATH环境变量,里面存储存着一个个路径(用冒号":"隔开),然后Linux每次在执行某些需要寻找某些内容的操作时(例如找一些可执行程序的位置),就会在逐个从这些路径中寻找对应的可执行程序文件,PATH变量的内容示例如下:

因为我们自定义的可执行程序基本上都不会在这些文件中,所以我们自定义的可执行程序一般需要手动指定文件路径的位置,而系统内置的指令对应的可执行程序肯定是设计好的,它们是一定会出现在PATH环境变量中的某个路径中的。

所以我们能知道,操作系统一般都会有一些环境变量,使用环境变量的目的在于执行某些操作时能够借助某些环境变量很便捷的完成(类似于用空间换时间)


包括上面提到的PATH环境变量,Linux中常见的环境变量有:

  • PATH : 命令的可搜索路径(一般是多个)。
  • HOME : 用户的主工作目录地址,即家目录的地址。
  • SHELL : 当前系统的shell是哪个,例如centos下的SHELL变量的通常为"/bin/bash"。
  • HOSTNAME:当前的主机名。
  • LOGNAME:当前的用户名。
  • HISTSIZE:可记录的历史命令的最大数量。
  • PWD:当前所在路径(pwd指令就是依赖的这个环境变量)。

本地变量和环境变量

  • 本地变量:本地变量是一种临时的变量,仅针对当前用户的当前进程生效,不会被子进程继承下去,定义格式为:"变量名=变量值",例如
local_val=local
  • 环境变量:环境变量是一种持续存在的变量,会对当前进程以及子进程生效。可以通过export将本地变量提升为环境变量,或者直接定义一个环境变量。

外部命令和内建命令

  • 外部命令:外部命令是一个独立的外部可执行程序,因为实用程序的功能通常都比较强大,程序量也相对较大,所以在系统加载时并不随系统一起被加载到内存中,而是保存在磁盘中,通常放在/bin,/usr/bin,/sbin,/usr/sbin……等目录下。当外部命令被调用时,本质就是调用了一个进程,此时Shell会创建一个子进程,这个子进程就是这个命令对应的进程。常见外部命令有:/bin/ls、vi、tee、tar等。
  • 内建命令:内部命令实际上是Shell程序的一部分,由 Shell 软件内部进行实现的命令,其中包含的是一些比较简单的linux系统命令,由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留在系统内存中。内部命令嵌入在Shell程序中,并不单独以磁盘文件的形式存在于磁盘上,其执行速度比外部命令快。常见的内建命令有:exit,history,cd,echo,fg,cd、source、export、time等。

内容参考:Linux Shell 内部命令与外部命令 - 知乎 (zhihu.com)

环境变量的相关操作

首先,在编写C\C++时,可以通过如下三种方式来获取环境变量

  1. getenv函数,用于获取指定的环境变量内容

    用法示例:
    char* path = getenv("PATH");
    printf("PATH : %s\n",path);
    
    /* 运行结果如下
    PATH : /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoming/.local/bin:/home/xiaoming/bin
    */
  2. putenv函数,导入环境变量。参数只有一个char*,表示要导入的环境变量。默认放到环境变量表的最后。如果与系统环境变量冲突,那么就会发生覆盖,相对于修改操作。
    用法示例:
    putenv("MYENV=12345");
  3. 第三个命令行参数 —— char* env[]。env是一个指针数组,数组的最后一个元素是NULL。

    而数组中的内容为每一个环境变量和对应的内容,格式为"环境变量=环境变量的内容",例如:
    PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoming/.local/bin:/home/xiaoming/bin
    用法示例如下:
    int main(int argc, char* argv[], char* env[])
    {
        for(int i = 0; env[i] != NULL; i++)
        {
            printf("%s\n",env[i]);
        }
    
        return 0;
    }
    
  4. unistd.h头文件中的一个外部链接属性的全局变量char** environ,envrion本质上就是指向的env数组的内容,需要显示的用extern声明才能使用。需要注意的是,由于env数组中存储的是char*的内容,因此char** environ群殴事件哦就相当于(char*)*envrion,所以这里不使用数组指针而是二级指针并没错。用法示例如下:
    int main()
    {  
        extern char** environ;
        for(int i = 0; environ[i] != NULL; i++)
        {
             printf("%s\n",environ[i]);
        }
    
        return 0;
    }
    

其次,还可以在控制台下通过一些指令来获取环境变量

  1. echo直接打印环境变量。环境变量本质上就是bash下的一个变量,因此可以直接用echo指令输出环境变量的内容。用法示例如下:
    echo $PATH
    echo ${PATH}
  2. env指令,列出所有的环境变量。env指令可以列出当前环境下的所有环境变量,通常也配合gerp检索单个环境变量,用法示例如下:
    env
    env | grep PATH

最后,还有一些其它的相关指令

  1. export,修改环境变量或增加环境变量。export的使用格式如下:
    # 将VAR环境变量的值设为val,如果VAR存在就是修改环境变量,不存在就是新增环境变量
    export VAR=val #注意,没有空格
    
    # 将VAR变量提升为环境变量(前提是VAR已存在)
    export VAR

    参考:export命令 - Linux命令大全(手册) (linuxcool.com)

  2. set,查看所有变量,包括本地变量和环境变量,至于何为本地变量,后面会说。

  3. unset,删除本地变量或环境变量。用法示例:

    # 删除myval变量
    unset myval
    

环境变量的本质

书接上回,Linux内置的指令所对应的可执行程序不用指定路径位置,是因为PATH环境变量中存储了它们所在的目录地址。那么如果我们想要运行自定义的可执行文件时,通常有两种解决方案:一是把我们的可执行文件放在PATH环境变量中的某个目录下,二是将可执行文件所在的目录地址添加到PATH环境变量中。

因为环境变量本质上就是bash下的一个变量,因此可以直接使用bash脚本的语法对其进行读写操作,例如:

# 在PATH中新增一个路径,$PATH表示之前的变量值
# 之后的 ":new_path" 就是按照PATH环境变量的格式新增的路径
PATH=$PATH:new_path   

但是这种方式修改的环境变量只能在当前登录时使用,修改的是内存中的环境变量,一旦重新登入Linux时,环境变量都会被重置,即我们对其做的所有操作都会消失,环境变量又会回到原处。这是因为,操作系统创建的环境变量是根据一些配置文件创建的,所以如果想要永久增添或者删除某些环境变量,就需要对这些配置文件进行操作,其中Linux下和环境变量相关的配置文件有:

1、系统位置,/etc目录下的:bashrc、profile、environment,三个文件。

2、用户目录下的:.bashrc、.bash_profile,两个文件。

内容参考:Linux环境变量到底配置到那里?-CSDN博客

那么这是为什么呢?首先,在命令行下直接修改PATH变量时只适用于本次登录,这是因为此时修改的PATH是bash进程内部的环境变量(bash本质上也是一个进程),之后重新登陆操作系统时,都会给我们形成新的bash解释器,并且新的bash解释器会自动重新形成自己的环境变量信息,那么自然之前修改的就消失了。

也就是说,每次登陆操作系统时,操作系统都会根据环境变量的相关文件中的信息,来为我们的bash进程生成一张环境变量表信息。那么为什么bash下启动的进程也可以正常使用环境变量呢?这是因为环境变量是可以继承给子进程的。子进程在创建时会自动继承(复制与父进程相同的环境变量)父进程的环境变量等信息,而我们前面知道,bash进程可以说是所有命令行下启动的进程的父进程,所以在bash下启动的进程都会继承bash进程的这个环境变量表。

那么bash进程的环境变量表又是怎么来的呢?那肯定是继承自它的父进程,那么它父进程的环境变量也是继承自其父进程的(爷爷进程)……,那么我们能知道,其实这个环境变量表的信息最开始就是一个操作系统启动初期的一个进程读取了这个配置文件中的信息,此后的子进程等都会直接继承这个环境变量的信息表,所以其实环境变量的信息传递,就类似是一个树状的结构。也就是说,包括bash在内的操作系统中的所有进程,都会存储一张环境变量表。

所以其实系统在启动我们的程序时,都会为这个新的进程提供两张表:命令行参数表和环境变量表,它们都能被子进程所继承。那么命令行参数和环境变量是如何做到被子进程所继承的呢?这就涉及到的虚拟地址空间的内容了。大致原因就是,每个进程创建时,都会有命令行参数表和环境变量表,而子进程在创建时,会拷贝父进程大部分数据,包括进程PCB、页表等内容,这其中也包含了命令行参数表和环境变量表(内容参考:为什么子进程能继承父进程的环境变量)。

所以回过头来看,其实命令行参数和环境变量并不是因为可以通过main函数传参、系统调用等方式获取,而是本来就有命令行参数表和环境变量表,使得我们可以通过这些方式获取对应的命令行参数或者环境变量的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值