Linux 下命令行参数和环境变量
- 命令行参数
- 为什么要有命令行参数
- 谁可以做到
- 结论
- 环境变量
- 一些现象
- 查看环境变量
- 添加环境变量
- 添加内存级环境变量
- 永久有效
- 其他环境变量
- HOME
- PWD
- SHELL
- HISTSIZE
- 自定义环境变量
- 定义
- 取消
- 本地变量
- 整体理解环境变量
- 环境变量的组织方式
- 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 系统指令似乎并不用带上路径,类似 pwd
, ls
等等,而我们的程序却要带上路径才能运行,成为进程,这是为什么呢?
其实 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
亲自执行才可,相互印证