Linux下 进程(五)(命令⾏参数和环境变量)

欢迎来到我的频道 【点击跳转专栏】
码云链接 【点此转跳】
请添加图片描述

Linux进程相关的所有内容(可以直接点击转跳):
一: Linux进程概念相关:

  1. 【Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)】
  2. 【 Linux下 进程(二)(进程状态、僵尸进程和孤儿进程)】
  3. 【Linux下 进程(三)(进程的优先级)】
  4. 【Linux下 进程(四)(进程的组织、进程的切换和进程O(1)调度算法)】
  5. 【Linux下 进程(五)(命令⾏参数和环境变量)】
  6. 【Linux下 进程(六)(程序地址空间)】

二:Linux进程控制相关:

  1. 【Linux下 进程控制(一) —— 进程的创建、终止和等待】
  2. 【Linux下 进程控制(二) —— 进程程序替换】
  3. 【Linux下 进程控制(三) —— ⾃主Shell命令⾏解释器】

1. 命令行参数

1.1 是什么

main函数能不能有参数?

答案是有的

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

int main(int argc, char *argv[])
{
    printf("argc: %d\n", argc);
    int i = 0;
    for(; i< argc ; i++)
    {
        printf("argv[%d]->%s\n", i, argv[i]);
    }
    return 0;
 }  
部分说明
argcArgument Count:命令行参数的个数(包括程序名本身)
argvArgument Vector:一个字符串数组,每个元素是一个命令行参数

运行结果:请添加图片描述


它的本质,就是提取你输入的字符串(数字会自动转换成char) )然后根据空格断开 自动提取对应的内容 存入到char* argv[]数组中:
在这里插入图片描述
⚠️:不过需要注意的是,我们是看不到这个过程的,这个是通过bash和系统自动完成的!

1.2 为什么要有

看个例子就明白了:

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

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("该命令使用错误! 你应该这样用: %s -a|-b|-c|-d\n", argv[0]);
        return 1;
    }

    if(strcmp(argv[1], "-a") == 0)
    {
        printf("我现在执行的是该命令的第一种功能\n");
    }
    else if(strcmp(argv[1], "-b") == 0)
    {
        printf("我现在执行的是该命令的第二种功能\n");
    }
    else if(strcmp(argv[1], "-c") == 0)
    {
        printf("我现在执行的是该命令的第3种功能\n");
    }
    else
    {
        printf("我现在执行的是该命令的默认功能\n");
    }
    return 0:
}
    

运行结果:
在这里插入图片描述
是不是跟ls -als -lls -n等等特别像!

💡:命令行参数本质应用,是为了实现一个命令,可以根据不同的选项,实现不同的子功能!也是Linux中所有命令选项功能的实现方式!!!

1.3 有关命令行参数的3个细节

  1. 细节1:命令行参数,至少是1argc >=1argv[0]一定会有元素,指向的就是程序名
  2. 细节2:选项,是以空格分隔的字符串,一个字符,也是字符串!
  3. 细节3:参数有argc个,argv[argc-1]是最后一个字符串,而argv[argc] == NULL

2.环境变量

2.1 环境变量的基本概念

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,属于系统级别的变量,变量名和变量内容,往往具有全局性

  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

2.2 常⻅环境变量

2.2.1 PATH&&查看和修改环境变量⽅法

在我们日常输入命令的时候,只需要像ls一样直接输入命令就行了,而我们自己写的程序却必须要加上./这是为什么呢?

系统的命令大多都是C语言写的可执行文件,并且放在了usr/bin路径下,不要带./即可跑,这是因为它在usr/bin路径,是系统默认会自动匹配寻找的路径;如果我们把我们自己写的code文件,拷贝到usr/bin路径下,我们发现我们也能直接不带./就能运行!
请添加图片描述
这是因为我们系统执行程序前,需要将程序加载到内存中,在加载前,系统得找到程序在哪里,不过系统不会在当前目录下找,而是在特定路径下找!./本质是告诉系统你的程序在哪里!`


Linux系统怎么知道在哪个路径寻找?谁告诉你的?还会不会去别的路径找呢?
Linux存在一个叫环境变量的东西是一个在全系统中全局有效的变量,属于shell语法自己定义的变量!而这里起作用的变量就叫:PATH

  • PATH : 指定命令的搜索路径,就是告诉吸引,如果用户执行一个可执行文件,没有指明路径,就会去PATH所指向的路径一个个找!

PATH是其的变量名,我们可以用echo $环境变量名称查看内容(以作为分隔符):请添加图片描述


我们可以用 环境变量名=XXX的方式直接覆盖环境变量就行了请添加图片描述
我们发现code可以直接运行,而ls无法运行了 这里直接把终端重启就恢复了


我么可以用环境变量名=$环境变量名:XXX的方式增加新的内容,这样原本的命令(ls等)也不会受到影响了
在这里插入图片描述

2.2.2 env(查看所有环境变量)

env : 显⽰所有环境变量

下面会展示显示内容并对变量添加介绍注释!

fcy@VM-16-15-ubuntu:~$ env

# 当前用户使用的 shell 程序
SHELL=/bin/bash

# 历史命令保存的最大条数(此处为 1000 条)
HISTSIZE=1000

# 历史命令时间戳格式:%F 表示 YYYY-MM-DD,%T 表示 HH:MM:SS
HISTTIMEFORMAT=%F %T 

# 当前工作目录(即用户执行 env 命令时所在的路径)
PWD=/home/fcy

# 当前登录用户名(等同于 whoami 的输出)
LOGNAME=fcy

# 当前会话类型(tty 表示通过终端/SSH 登录,而非图形界面)
XDG_SESSION_TYPE=tty

# 表示系统在登录时已显示“每日提示”(Message of the Day, MOTD)
MOTD_SHOWN=pam

# 用户主目录路径
HOME=/home/fcy

# 系统语言和字符编码(美式英语 + UTF-8)
LANG=en_US.UTF-8

# ls 命令中不同文件类型的显示颜色配置(例如目录蓝色、可执行文件绿色等)
LS_COLORS=rs=0:di=01;34:ln=01;36:...(省略部分为各类文件扩展名的颜色规则)

# 每次命令执行后自动追加到历史文件(确保多终端间历史同步)
PROMPT_COMMAND=history -a; history -a; 

# SSH 连接信息:客户端IP 客户端端口 服务器IP 服务器端口
SSH_CONNECTION=49.80.114.131 29107 10.0.16.15 22

# less 命令关闭时调用的脚本(用于清理临时文件等)
LESSCLOSE=/usr/bin/lesspipe %s %s

# 会话类别(普通用户会话)
XDG_SESSION_CLASS=user

# 终端类型(支持 256 色的 xterm 兼容终端)
TERM=xterm-256color

# less 命令打开文件前调用的预处理器(如解压 .gz 文件再查看)
LESSOPEN=| /usr/bin/lesspipe %s

# 当前用户名(与 LOGNAME 相同)
USER=fcy

# Shell 嵌套层级(1 表示这是最外层 shell)
SHLVL=1

# 当前会话的唯一 ID(由 systemd-logind 分配)
XDG_SESSION_ID=301948

# 用户运行时目录(存放 socket、pid 等临时运行文件)
XDG_RUNTIME_DIR=/run/user/1006

# SSH 客户端连接信息:IP 地址、源端口、目标端口
SSH_CLIENT=49.80.114.131 29107 22

# 应用程序数据目录搜索路径(用于查找图标、桌面文件等)
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop

# 系统可执行程序的搜索路径(最后多了自定义路径)
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/fcy/linux_code/lesson18

# D-Bus 会话总线地址(用于进程间通信)
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1006/bus

# 当前 SSH 会话绑定的伪终端设备
SSH_TTY=/dev/pts/0

# 最后执行的命令路径(这里显示的是 /usr/bin/env 本身)
_=/usr/bin/env

2.2.3 浅谈上述打印的环境变量

我们可以用history查看历史命令 而其记录的上线就是通过HISTSIZE这个环境变量控制的!

  • HISTSIZE:历史命令保存的最大条数。

而当我们whoamipwd的时候 本质也是打印对应的环境变量USERPWD

  • USER:当前用户名
  • PWD:当前工作目录

其中SHELL=/bin/bash,我们cd ~为什么能回到家目录 也是因为HOME 记录了我么的家目录位置。

  • SHELL : 当前Shell,它的值通常是/bin/bash(也就是具体的shell是什么)
  • HOME : 指定⽤⼾的主⼯作⽬录

⚠️:其实还有很多比如 cd -等等 但原理大差不差就不写了!

2.3 通过代码如何获取环境变量

方法可移植性是否需要 main 参数说明
char *env[] in main❌(仅 POSIX/GCC)直接遍历所有环境变量
extern char environ❌(POSIX)全局变量,可在任意函数使用
getenv("KEY")✅(C 标准)只能查单个变量,最安全

2.3.1 命令⾏第三个参数

其实在main中还存在第三个命令行参数char* env[]

参数含义
argc命令行参数个数(argument count)
argv命令行参数数组(argument vector),argv[0] 是程序名
env环境变量表(environment vector),以 NULL 结尾
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

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

        printf("这是我自己实现的命令\n");
        return 0;
}

运行结果:
在这里插入图片描述
本质是把环境变量表传递给了进程,每个程序都会收到⼀张环境表,环境表是⼀个字符指针数组,每个指针指向⼀个以’\0’结尾的环境字符串:请添加图片描述

2.3.2 通过第三⽅变量environ获取

在C\C++标准库中,libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头⽂件中,所以在使⽤时 要⽤extern声明。请添加图片描述

extern:在 C 和 C++ 中,extern 是一个存储类说明符,用于声明变量或函数是在其他编译单元中定义的,当前文件只是“引用”它,而不是“定义”它,通俗讲就是告诉编译器:“这个符号在别处定义,链接时请去别处找。常用于声明外部全局变量

关键字链接属性作用
extern外部链接全局可见,可在其他文件使用
static(全局变量/函数)内部链接仅本文件可见,隐藏实现
static int secret = 100;   // 其他文件无法访问,包头文件也不行!
extern int shared = 200;   // 其他文件可通过 extern 声明使用,不用包头文件!
特性static int secret = 100(静态全局变量)extern int shared = 200(外部全局变量)
作用域仅当前编译单元(.c/.cpp 文件),属于「文件级私有」整个程序(所有编译单元),属于「程序级共享」
链接属性内部链接(internal linkage)外部链接(external linkage)
其他文件访问方式无法访问(即使 #include 头文件/extern 声明)只需在其他文件写 extern int shared; 即可访问(无需包含头文件)
内存存储全局/静态存储区(同全局变量)全局/静态存储区
重复定义风险不同文件可定义同名 static 全局变量(互不干扰)整个程序中只能定义一次(重复定义会报链接错误)
int main()
{
extern char **environ;
    int i = 0;
    for(; environ[i]; i++)
    {
        printf("environ[%d]->%s\n", i, environ[i]);
    }
    return 0;
}

我们发现也能正常打印:
请添加图片描述

2.3.3 getenv(最常用的方法)

请添加图片描述

#include <stdlib.h>
char *getenv(const char *name);

原理就是遍历环境变量表 获取对应的环境变量getenv常来访问特定的环境变量。

int main()
{
   char *whoami = getenv("USER");
   printf("whoami: %s\n", whoami);
   if(whoami == NULL)
   {
       printf("无法执行我,因为我不认识你!\n");
       return 0;
   }
   else if(strcmp(whoami, "root") == 0)
   {
       printf("我也不准root执行!\n");
   }
   else if(strcmp(whoami, "fcy") == 0)
   {
       printf("你是合法的用户,我让你执行\n");
   }
 }  

fcy账户执行:请添加图片描述
root执行:请添加图片描述

2.4 环境变量的全局属性

我们所知的argv和env都是默认在bash内部的,而bash本身是个进程,所以这两张表本质是在内存中的两张临时的表!而众所周知 我的程序命令等,都是bash进程的子进程!而子进程是可以共享父进程的代码和数据的(如果子进程不改数据的话)。
所以我们可以这么理解:全局变量表本质就是bash进程的数据!当我们运行自己的程序的时候,bash就会通过fork创建子进程,而环境变量具有全局属性,此时

  • 环境变量不是某个函数或类的局部数据,而是属于整个进程;
  • 无论你在 main()、某个工具函数、还是第三方库中调用 getenv("PATH"),都能拿到相同的值。

是可以直接被⼦进程继承下去(在未修改的情况下)!这就是哪怕我们不传char* env[]参数依然可以通过另外两个方法拿到全局变量的参数的原因!

2.4.1 bash进程的环境变量怎么来的

那么bash进程的环境变量又是从哪里来的呢?

是从Linux的系统配置文件下获得的。


那么配置文件是从哪里获取的?
请添加图片描述
一般在家目录的.bashrc中获取,然后通过.bashrc读取系统的配置文件(一般在/etc路径下)

2.4.2 export(创建环境变量)

  • export:可以将本地变量转换为全局变量,本质是把局部变量写到环境变量表里,属于内存级操作,当再次打开bash会重置!若想要永久存在,则需要修改系统的配置文件
  • 本地变量:无法被子进程继承,不具备全局性,只能在bash内部被访问的变量。

在这里插入图片描述

2.4.3 unset(删除环境变量)

unset: 清除环境变量,将对应环境变量从表中删除,本质还是内存级,不影响配置文件。

在这里插入图片描述

2.4.4 set(查看环境和局部变量)

set: 显⽰本地定义的shell变量和环境变量。

请添加图片描述

2.4.5 echo怎么打印本地变量的?

  • 按照上面的说法 我们的echo应该也是bash的一个子进程,而子进程按道理来说是没办法继承局部变量的!但下图的echo依然可以打印局部变量。
  • 我们还发现在修改PATH的情况下,有些命令还依然能跑,这是为什么呢??

在这里插入图片描述

⚠️:这里可以得出个结论(目前了解这么多就行):命令和命令是不一样的!存在二进制文件级别的普通命令!是磁盘级,会加载成子进程然后执行同时也存在内建命令:在shell内部自己定义,不创建子进程,只是bash内部的的一次函数调用,不依赖第三方途径。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值