Linux:Shell环境变量与命令行参数
目录
Shell的变量功能
什么是变量
变数的可变性与方便性
影响bash环境操作的变量
脚本程序设计(shell script)的好帮手
变量的使用:echo
变量的使用:HOME
环境变量相关命令
获取环境变量
环境变量和本地变量
命令行参数
什么是命令行参数
命令行参数有什么用
环境变量与命令行参数
Shell的变量功能
什么是变量
变数的可变性与方便性
影响bash环境操作的变量
脚本程序设计(shell script)的好帮手
变量的使用:echo
[root@www ~]# echo $variable
[root@www ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
[root@www ~]# echo ${PATH}
----------------------------------------------------------------------
[root@www ~]# echo $myname
<==这里幵没有任何数据~因为这个变量尚未被设置!是空的!
[root@www ~]# myname=VBird
[root@www ~]# echo $myname
VBird <==出现了!因为这个发量已经被设定了!
若该变量需要扩增变量内容时,则可用 "$变量名称" 戒 ${变量} 累加内容,如下所示:
『PATH="$PATH":/home/bin』
我们知道执行一个程序时,是需要./mytest 的,但下面这样设置之后,直接mytest,不用带 ./ ,当前路径该程序就可以直接运行。
- 方法 1:把 proc 拷贝到环境变量 PATH 指定的任一路径下。
- 方法 2:把 proc 所在路径添加到环境变量 PATH 中。(注意:自己添加的环境变量,系统重启后是不会被保存的)
$ PATH=$PATH:/home/ll/xxx/10 # :冒号是分隔符 # 注意:PATH=/home/ll/xxx/10,不能这样写,必须加上$符号,否则会把PATH中所有内容覆盖掉
效果图:
变量的使用:HOME
分别在 root 和普通用户下执行 cd ~ 和 pwd 查看家目录,分别是 /root 和 /home/test,为什么得到的结果不一样呢?因为不同用户的家目录中的配置文件不一样,所以不同用户下的环境变量 HOME 也是不一样的。
[root@VM-0-12-centos ~]$ echo $HOME
/root
[test@VM-0-12-centos ~]$ echo $HOME # 指定用户的主工作目录
/home/test
环境变量相关命令
- echo:显示某个环境变量值。
- export:设置一个新的环境变量。
- env:显示所有环境变量。
- unset:清除环境变量。
- set:显示本地定义的 shell 变量和环境变量。
获取环境变量
getenv 和 setenv 函数介绍:
#include <stdlib.h>
char *getenv(const char *name); // 获取环境变量
int setenv(const char *name, const char *value, int overwrite); // 更改或添加环境变量
name:环境变量的名称
返回值:环境变量的内容
// proc.c
#include <stdio.h>
#include <stdlib.h> // getenv
int main()
{
printf("%s\n", getenv("PATH"));
printf("%s\n", getenv("HOME"));
printf("%s\n", getenv("SHELL"));
return 0;
}
代码通过 getenv
函数读取了 PATH
、HOME
、SHELL
等环境变量。这些变量的值继承自父进程(即启动该程序的进程)。
- 在命令行运行
./proc.c
时,父进程是 bash。 - bash 在启动时会加载自己的环境变量(例如从
~/.bashrc
),并在创建子进程(你的程序)时将这些变量传递给子进程。
当你在 bash 中输入命令时(如 ls
或 ./a.out
),bash 的典型行为是:
- fork():创建一个子进程(复制父进程 bash 的环境变量、内存状态等)。
- exec():在子进程中替换为新的程序(如
ls
或./a.out
)。 - 环境变量继承:子进程通过继承机制获得父进程(bash)的环境变量。
因此,你的程序中通过 getenv
读取到的环境变量,实际上是父进程 bash 传递下来的。那么就很好的引出了环境变量和本地变量的区别,具体请看如下。
环境变量和本地变量
环境变量(环境变量通常具有全局属性:可以被子进程继承下去)。环境变量实际上是在当前 bash 的上下文中定义的。所以在当前命令行解释器 bash 内可以被访问到,在 bash 创建的子进程内也可以被访问到。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char* env = getenv("MYENV");
if(env)
{
printf("%s\n", env);
}
return 0;
}
直接运行发现没有结果,说明该环境变量根本不存在。
- 导出环境变量:export MYENV="hello world"
- 再次运行程序,发现有结果了。说明:环境变量是可以被子进程继承下去的。
本地变量(只能在当前 shell 命令行解释器内被访问,不可以被子进程继承)
[ll@VM-0-12-centos 10]$ MY_VAL="hello world" # 定义本地变量(在bash内定义的)
如何查看本地变量呢?
[ll@VM-0-12-centos 10]$ echo $MY_VAL # 在当前命令行解释器bash内访问本地变量
hello world
如何证明本地变量不能被子进程继承?
// proc.c
#include <stdio.h>
#include <stdlib.h> // getenv
int main()
{
printf("%s\n", getenv("MY_VAL"));
return 0;
}
该怎么解决这个问题呢?可以将本地变量 MY_VAL 设置成环境变量。
[ll@VM-0-12-centos 10]$ exprot MY_VAL # 把本地变量MY_VAL导出成环境变量
上面说到,我们在命令行上运行的大部分命令,都是 bash 创建子进程来执行的,而本地变量不能被子进程继承,那为什么使用 echo 命令,却可以访问本地变量呢?
在Linux中的命令分类:
# 将PATH变量设置为空
[test@VM-16-11-centos ~]$ export PATH=""
# 以下命令均不能使用
[test@VM-16-11-centos ~]$ ll
-bash: ls: No such file or directory
[test@VM-16-11-centos ~]$ touch
-bash: touch: No such file or directory
[test@VM-16-11-centos ~]$ mkdir
-bash: mkdir: No such file or directory
# pwd仍然可以使用
[test@VM-16-11-centos ~]$ pwd
/home/test
- 常规命令:shell通过fork让子进程执行的
- 内建命令:shell命令行的一个函数,可以直接读取shell内部定义的本地变量
而在上面的测试中,像ls,mkdir这样的命令,都是shell通过fork创建子进程来执行的,而这里的PATH路径已经被用户破坏了,因此找不到搜索的路径,因此找不见是当然的事,但是为什么pwd可以找到?这是由于pwd这样的命令是内建命令,这是shell命令行的一个函数,可以直接读取shell内部的本地变量,因此就可以找到对应的值进行输出了。
会提供一个environ
指针,这个指针会指向一张环境表,环境表是一个字符指针数组,每一个指针都会有一个字符串。
命令行参数
什么是命令行参数
在学习C
语言中,对于main
函数当初的写法是没有任何参数的,但是实际上,main
函数是可以有参数的,比如下面的写法:
#include <stdio.h>
int main(int argc, char* argv[])
{
int i = 0;
for (i = 0; i < argc; i++)
{
printf("%d:%s\n", i, argv[i]);
}
return 0;
}
运行结果:
[test@VM-16-11-centos 10_15]$ vim myproc.c
[test@VM-16-11-centos 10_15]$ make
gcc -o myproc myproc.c
[test@VM-16-11-centos 10_15]$ ./myproc
0:./myproc
其实,命令行参数是用来支持各种指令级别的命令行选项的设置,例如这里的argv
数组中,存储的字符串其实就是用户写的命令,而如果现在我在运行程序的时候带上选项 :
[test@VM-16-11-centos 10_15]$ ./myproc -a -b -c -d
0:./myproc
1:-a
2:-b
3:-c
4:-d
所以推出:
int main(int argc, char *argv[]) {
// argc 表示参数总数(包括程序名本身)
// argv[0] 是程序名,argv[1] 是第一个参数,依此类推
}
并且运行结果发现打印的信息就是以用户写的指令开始进行分割,然后把信息放到数组中,从中其实看出,用户在运行程序的时候写的命令或者是带的选项都可以被main函数读取并且传参,函数体内部就可以利用这个机制实现不同的选项带来的结果。比如,Linux中有ls命令,用来查看文件夹中的内容,如果使用的是ls -a或者是ls -l这些选项,就会产生不同的结果,实际上这样的结果也是通过这个原理,通过读取argv数组中的内容就可以实现各种目的。
示例:ls
命令的选项实现逻辑
// 伪代码示意
#include<stdio.h>
#define SHOW_HIDDEN (1 << 0) // 二进制 0b01
#define LONG_FORMAT (1 << 1) // 二进制 0b10
int main(int argc, char *argv[]) {
int options = 0; // 初始化选项标志位
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-a") == 0) {
options |= SHOW_HIDDEN; // 设置显示隐藏文件标志
} else if (strcmp(argv[i], "-l") == 0) {
options |= LONG_FORMAT; // 设置长格式输出标志
}
}
// 根据 options 执行相应逻辑
list_directory(".", options);
return 0;
}
$ ./program
运行结果:file1.txt file2.txt dir1
$ ./program -a
运行结果:. .. .hidden_file file1.txt file2.txt dir1
$ ./program -l
运行结果:
-rw-r--r-- 1 user group 1024 Jan 1 10:00 file1.txt
drwxr-xr-x 2 user group 4096 Jan 1 10:00 dir1
$ ./program -a -l
运行结果:
drwxr-xr-x 2 user group 4096 Jan 1 10:00 .
drwxr-xr-x 5 user group 4096 Jan 1 10:00 ..
-rw-r--r-- 1 user group 0 Jan 1 10:00 .hidden_file
-rw-r--r-- 1 user group 1024 Jan 1 10:00 file1.txt
drwxr-xr-x 2 user group 4096 Jan 1 10:00 dir1
$ ./program -x
运行结果:
file1.txt file2.txt dir1
- 无参数:默认行为(不显示隐藏文件,短格式)。
-a
:显示隐藏文件。-l
:长格式显示文件元数据。-a -l
:同时启用两种模式。- 无效参数:被忽略,按默认模式运行。
命令行参数有什么用
为什么要存在命令行参数呢?
帮助我们能够给同一个程序,设计出不同的业务功能。
举个小例子,比如我想要实现这样一个计算器:
如果输入 ./cal,则会提示该程序的正确用法:Usage:./cal -[a|s] x y;输入 ./cal -a 1 2,cal 程序可以输出 1 + 2 的结果;
输入 ./cal -s 4 2,cal 程序可以输出 4 - 2 的结果。
#include <stdio.h>
#include <stdlib.h> // atoi -- 函数原型:int atoi(const char *nptr); // 将C字符串转换成整数
#include <string.h> // strcmp
// cal命令的用法手册
void Usage(const char* cal)
{
printf("Usage: %s -[a|s] x y\n", cal);
}
int main(int argc, char* argv[]) // 接收命令行参数
{
// 输入的参数个数不为4
if (argc != 4)
{
Usage(argv[0]);
return 1; // 退出程序
}
// 保存第3个和第4个参数
int x = atoi(argv[2]);
int y = atoi(argv[3]);
// 根据不同参数,执行不同功能,然后输出结果
if (strcmp(argv[1], "-a") == 0)
{
printf("%d + %d = %d\n", x, y, x + y);
}
else if (strcmp(argv[1], "-s") == 0)
{
printf("%d - %d = %d\n", x, y, x - y);
}
else
{
Usage(argv[0]);
return 1; // 退出程序
}
return 0;
}
运行结果:
[test@VM-0-12-centos 10]$ ./cal # 命令使用手册
Usage: ./cal -[a|s] x y
[test@VM-0-12-centos 10]$ ./cal -a 1 2 # 实现加法
1 + 2 = 3
[test@VM-0-12-centos 10]$ ./cal -s 4 2 # 实现减法
4 - 2 = 2
环境变量与命令行参数
main 函数除了可以传递两个和命令行参数相关的参数 argc 和 argv 以外,还可以传递第 3 个参数 env:
int main(int argc, char* argv[], char* env[]);
这也是 main 函数获取环境变量的方式。通过给 main 函数第三个参数传参,把一个个环境变量传递给当前程序,当前程序运行起来变成进程,就意味着当前这个进程获取到了这些环境变量。
每个被 bash 创建的子进程都会接收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以 ’\0’ 结尾的环境字符串(环境变量)。
// proc.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[], char* env[]) // 通过第三个参数接收环境变量
{
for (int i = 0; env[i]; i++) // 循环结束条件为env[i],遍历到NULL停止
{
printf("env[%d]: %s\n", i, env[i]); // 遍历字符指针数组env
}
return 0;
}
运行结果:获取到了当前 bash 内的所有环境变量。(因为环境变量被 bash 创建的子进程 proc 继承下去了)
所以现在知道 C 库函数 getenv 的实现原理了,比如 printf("%s\n", getenv("PATH"));,其实就是通过在字符指针数组 env 中进行字符匹配,找到 "PATH" 后面的内容并返回。