当前位置: 首页 > article >正文

Linux:Shell环境变量与命令行参数

目录

Shell的变量功能

什么是变量

变数的可变性与方便性

影响bash环境操作的变量

脚本程序设计(shell script)的好帮手

变量的使用:echo

变量的使用:HOME 

环境变量相关命令

获取环境变量 

环境变量和本地变量 

命令行参数 

什么是命令行参数

命令行参数有什么用 

环境变量与命令行参数 


Shell的变量功能

        变量是 bash 环境中非常重要的一个玩意儿,我们知道 Linux 是多人多任务的环境,每个人登录系统都能取得一个 bash shell, 每个人都能够使用 bash 执行 mail 这个命令来接收『自己』的邮件等。问题是,bash是如何得知你的邮箱是哪一个文件?这就需要变量的帮助。所以,你说变量重要不重要?下面我将介绍重要的环境变量、变量的使用与设置等数据。

什么是变量

        那么,什么是『变量』呢?简单的说,就是让某一个特定字符串代表不固定的内容就是了。举个大家在中学都会学到的数学例子, 那就是:『 y = ax + b 』这东西,在等号左边的 (y) 就是变量,在等号右边的(ax+b) 就是变量内容。 要注意的是,左边是未知数,右边是已知数喔! 讲的更简单一点,我们可以『用一个简单的 " 字眼 " 来取代另一个比较复杂或者是容易变动的数据』。这有什么好处啊?最大的好处就是『方便!』。

变数的可变性与方便性

        举例来说,我们每个账号的邮件信箱预设是以 MAIL 这个变量来进行存取的, 当 dmtsai 这个使用者登入时,他便会取得 MAIL 这个变量,而这个变量的内容其实就是 /var/spool/mail/dmtsai , 那如果vbird 登入呢?他取得的 MAIL 这个变量的内容其实就是 /var/spool/mail/vbird 。 而我们使用信件读取指令 mail 来读取自己的邮件信箱时,这支程序可以直接读取 MAIL 这个变量的内容, 就能够自动的分辨出属于自己的信箱信件啰!这样一来,设计程序的程序员就真的很方便!

        如上图所示,由于系统已经帮我们规划好 MAIL 这个变量,所以用户只要知道 mail 这个指令如何使用即可, mail 会主动的取用 MAIL 这个变量,就能够如上图所示的取得自己的邮件信箱了! ( 注意大小写,小写的 mail 是指令, 大写的 MAIL 则是变量名称喔! )
        那么使用变量真的比较好吗?这是当然的!想象一个例子,如果 mail 这个指令将 root 收信的邮件信箱(mailbox) 档名为 /var/spool/mail/root 直接写入程序代码中。那么当 dmtsai 要使用 mail 时,将会取得 /var/spool/mail/root 这个档案的内容! 不合理吧!所以你就需要帮 dmtsai 也设计一个 mail 的程序,将 /var/spool/mail/dmtsai 写死到 mail 的程序代码当中! 天吶!那系统要有多少个 mail 指令啊?反过来说,使用变量就变的很简单了!因为你不需要更动到程序代码啊! 只要将 MAIL 这个变量带入不同的内容即可让所有使用者透过 mail 取得自己的信件!当然简单多了!

影响bash环境操作的变量

        某些特定变量会影响到 bash 的环境!举例来说,我们前面已经提到过很多次的那个 PATH 变量! 你能不能在任何目录下执行某个指令,与 PATH 这个变量有很大的关系。例如你下达 ls 这个指令时,系统就是透过 PATH 这个变量里面的内容所记录的路径顺序来搜寻指令的呢!如果在搜寻完 PATH 变量内的路径还找不到 ls 这个指令时, 就会在屏幕上显示『 command not found 』的错误信息。
        如果说的专业一点,那么由于在 Linux System 下面,所有的线程都是需要一个执行码, 而就如同上面提到的,你『真正以 shell 来跟 Linux 沟通,是在正确的登入 Linux 之后!』这个时候你就有一个 bash 的执行程序,也才可以真正的经由 bash 来跟系统沟通!而在进入 shell 之前,也正如同上面提到的,由于系统需要一些变量来提供他数据的读写 (或者是一些环境的设置参数值,例如是否要显示彩色等) ,所以就有一些所谓的 『环境变量』 需要来读入系统中了!这些环境变量例如 PATH、HOME、MAIL、SHELL 等等,都是很重要的, 为了区别与自定义变量的不同,环境变量通常以大写字符来表示!

脚本程序设计(shell script)的好帮手

        这些还都只是系统默认的变量的目的,如果是个人的设定方面的应用呢:例如你要写一个大型的 script 时,有些数据因为可能由于用户习惯的不同而有差异,比如说路径好了,由于该路径在 script 被使用在相当多的地方,如果下次换了一部主机,都要修改 script 里面的所有路径,那么我一定会疯掉! 这个时候如果使用变量,而将该变量的定义写在最前面,后面相关的路径名称都以变量来取代, 嘿嘿!那么你只要修改一行就等于修改整篇 script 了!方便的很!所以,良好的程序设计师都会善用变量的定义!

        最后我们就简单的对『什么是变量』作个简单定义好了: 『变量就是以一组文字或符号等,来取代一些设定或者是一串保留的数据!』, 例如:我设定了『myname』就是『 VBird 』,所以当你读取 myname 这个变量的时候,系统自然就会知道!那就是 VBird ! 那么如何『显示变量』呢?这就需要使用到 echo 这个指令!
        说的口沫横飞的,也不知道『变量』与『变量代表的内容』有啥关系? 那我们就将『变量』的『内容』拿出来给您看看。你可以利用 echo 这个指令来取用变量, 但是,变量在被取用时,前面必须要加上钱字号『 $ 』才行,举例来说,要知道 PATH 的内容,该如何是好?

变量的使用:echo

我们这里单纯是拿 echo 来读出变量的内容而已,更多的 echo 使用,执行指令 man 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

环境变量相关命令

  1. echo:显示某个环境变量值。
  2. export:设置一个新的环境变量。
  3. env:显示所有环境变量。
  4. unset:清除环境变量。
  5. 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 函数读取了 PATHHOMESHELL 等环境变量。这些变量的值继承自父进程(即启动该程序的进程)。

  • 在命令行运行 ./proc.c 时,父进程是 bash
  • bash 在启动时会加载自己的环境变量(例如从 ~/.bashrc),并在创建子进程(你的程序)时将这些变量传递给子进程。

当你在 bash 中输入命令时(如 ls 或 ./a.out),bash 的典型行为是:

  1. fork():创建一个子进程(复制父进程 bash 的环境变量、内存状态等)。
  2. exec():在子进程中替换为新的程序(如 ls 或 ./a.out)。
  3. 环境变量继承:子进程通过继承机制获得父进程(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" 后面的内容并返回。 


http://www.kler.cn/a/564597.html

相关文章:

  • 29.[前端开发-JavaScript基础]Day06-创建对象-对象补充-包装类型-数组Array
  • 神经网络 - 函数饱和性、软性门、泰勒级数
  • CSS 实现波浪效果
  • Python 的基础语法
  • 基于 ‌MySQL 数据库‌对三级视图(用户视图、DBA视图、内部视图)的详细解释
  • React + TypeScript 复杂布局开发实战
  • 【落羽的落羽 数据结构篇】链式结构的二叉树
  • 计算机视觉 |解锁视频理解三剑客——SlowFast
  • mysql架构查询执行流程(图解+描述)
  • Redis离线安装(内网,源码安装)
  • 小白向-python实现插入排序算法
  • 大白话React Hooks,新特性怎么用?
  • Trae根据原型设计稿生成微信小程序密码输入框的踩坑记录
  • 使用优化版的编辑距离算法替代ES默认的评分算法
  • 结构体介绍及内存大小分配问题
  • 有没有什么免费的AI工具可以帮忙做简单的ppt?
  • 【C++ 真题】P2661 [NOIP 2015 提高组] 信息传递
  • 【JavaSE-1】初识Java
  • QT:QT场景视图
  • 材料分析方法和特点