【把Linux“聊”明白】命令行参数与环境变量

命令行参数与环境变量

友情专栏:【把Linux"聊"明白】


文章目录

  • 命令行参数与环境变量
  • 前言
  • 一、命令行参数
  • 二、环境变量
    • [2-1 基本概念](#2-1 基本概念)
    • [2-2 常见环境变量](#2-2 常见环境变量)
    • [2-3 查看环境变量方法及探索环境变量的的本质(实验)](#2-3 查看环境变量方法及探索环境变量的的本质(实验))
    • [2-4 认识更多的环境变量](#2-4 认识更多的环境变量)
    • [2-5 和环境变量相关的命令](#2-5 和环境变量相关的命令)
    • [2-6 环境变量的组织方式](#2-6 环境变量的组织方式)
    • [2-7 通过代码如何获取环境变量](#2-7 通过代码如何获取环境变量)
    • [2-8 通过系统调用获取或设置环境变量](#2-8 通过系统调用获取或设置环境变量)
    • [2-9 环境变量通常是具有全局属性的](#2-9 环境变量通常是具有全局属性的)
    • [2-10 环境变量的来源](#2-10 环境变量的来源)

前言

Linux中,为什么 ls 命令能直接运行,而我们自己的程序却要用 ./hello?这背后是环境变量在起作用。环境变量就像系统的"记忆卡片",记录了各种程序运行需要的信息。

本文将用简单例子带你搞懂环境变量的核心,从基础操作到底层原理,让你真正掌握这个实用技能。


一、命令行参数

在我们的代码中,对于main函数都是不带参数的,那main函数真的没有参数吗?

不是的,其实main函数是可以有参数的。

这是标准规定的两种参数的形式:

c 复制代码
int main(int argc, char *argv[])

对于三种参数的main函数,大多数编译器也是支持的:

c 复制代码
int main(int argc, char *argv[], char *envp[])

但是不代表我们写的无参的main函数,是错误的,因为main函数有参数是后续的标准规定的,它要具有向前兼容性。

两种参数的main函数,我们先来写个代码看看这么个事:

c 复制代码
#include <stdio.h>

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

输出:

可见,bash把我们的命令行输入的命令给我们拆分了(按照空格),然后传给main函数的参数,其中,argc为参数总数,argv为每个参数。

然后再给我们再想想,前面的 ls -a -l.......什么原理?


ls它也是二进程文件,和我们的可执行文件是一样的,同样,也是有main函数的,那他是怎么辨别ls ls -a ls -a -l...呢?显然,是命令行参数。

当然,我们可以基于我的上述的代码来模拟一下main命令行参数是如何实现此等功能的(我只是简单的提下思想)。

c 复制代码
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    if (argc == 1)
    {
        printf("%s\n ", argv[0]);
    }
    else if (argc == 2)
    {
        if (strcmp(argv[1], "a"))
            printf("%s %s\n ", argv[0], argv[1]);
        else if (strcmp(argv[1], "b"))
            printf("%s %s\n ", argv[0], argv[1]);
        else
        { //...
        }
    }
    else 
    {
        //...
    }
    return 0;
}

可见,main命令行参数就是程序实现不同子功能的方法!

基于上述我们会发现,在我们的进程中存在一张表,我们称之为命令行参数表argv。可以用来支持选项功能。

对于三种参数的main函数,在环境变量中会说。

二、环境变量

2-1 基本概念

环境变量(environmentvariables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

如:我们在执行ls命令的时候,我们从来不知道ls的二进制可执行文件在哪里,但是照样可以执行成功原因就是有相关环境变量帮助我们进行查找。

环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。

2-2 常见环境变量

PATH:指定命令的搜索路径
HOME:指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL:当前Shell,它的值通常是/bin/bash

2-3 查看环境变量方法及探索环境变量的的本质(实验)

echo $NAME

注:NAME为环境变量名

我们可以直接来写个hello.c来测试一下,编译后生成可执行文件hello:

c 复制代码
#include <stdio.h>

int main()
{
    printf("hello world\n");
    return 0;
}

为什么./hello可以执行和而hello不可以执行


平时我们都在用./XXX来执行我们的二进制程序,而XXX却不可以执行我们自己的二进制程序,为什么?

我们的二进制程序是带了路径 的,我们在执行 ls ...为什么可以直接执行,不需要带路径呢?

此时就不得不提到我们的环境变量PATH !!!

可以先来查看一下 echo $PATH

我们平时使用的ls...大多数指令,它们也是要有路径的,只是它们的路径来自于PATH,我们可以这样来执行:

此时,聪明的你就要说了,那我们可不可以把我们的二进制程序放到/usr/bin下呢?可不可以把我们的当前路径加入到PATH呢?

当然可以,都可以实现!!!

  1. 把我们的二进制程序放到/usr/bin
  1. 将我们的程序所在路径加入环境变量PATH当中:export PATH=$PATH:hello程序所在路径(命令下面会说)

其实到此,对于环境变量,至少PATH环境变量,我们可以理解了。其它的环境变量我们下面会说的。

2-4 认识更多的环境变量

下面我们来看一下其它的环境变量,可以用命令env显示所有环境变量(由于某种原因,以下是用ai生成的):

shell 复制代码
$ env
USER=alice
LOGNAME=alice
HOME=/Users/alice
SHELL=/bin/zsh
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
TERM=xterm-256color
TMPDIR=/var/folders/.../
LANG=en_US.UTF-8
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
SHLVL=1
PWD=/Users/alice
OLDPWD=/Users/alice/Downloads
__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0
_=/usr/bin/env

其中的PATH我们前面已经见过了;其它:

USER(当前用户名)、PWD(当前工作目录)......

我们可以自己在本地用用。

2-5 和环境变量相关的命令

  1. echo:显示某个变量值(本地变量或者环境变量)
  2. export: 设置一个新的环境变量

export会覆盖掉已经存在的环境变量,所以前面我会export PATH=$PATH:hello程序所在路径写,

  1. env: 显示所有环境变量
  2. unset: 清除环境变量
  3. set: 显示本地定义的shell变量和环境变量

set 显示当前 shell 中定义的所有变量,包括只有当前 shell 能用的本地变量 ,以及会传给子进程的环境变量

此时,我们可以用export来将本地变量 i 提到环境变量中。

那两者的区别在何处呢?环境变量是可以被进程继承下去的(下面会说)。

2-6 环境变量的组织方式

对于前面的命令行参数,我们说bash有一个命令行参数表;

此时,对于环境变量来说,bash也有一个环境变量表,环境表其实就是⼀个字符指针数组,也是以NULL指针结尾。

environ解释:

environ 是一个全局变量指针,指向环境变量表的起始位置。

c 复制代码
// 标准声明
extern char **environ;

environ是libc中定义的全局变量,没有包含在任何头文件中,所以在使用时要用extern声明。

2-7 通过代码如何获取环境变量

  1. ** 命令行第三个参数**
    前面我们说过命令行也可以有第三个参数,即环境变量表,看代码:
c 复制代码
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
    for (int i = 0; env[i]; i++)
    {
        printf("%s\n", env[i]);
    }
    return 0;
}
  1. 通过全局变量environ获取
c 复制代码
#include <stdio.h>
extern char **environ;
int main(int argc, char *argv[])
{
    int i = 0;
    for (; environ[i]; i++)
    {
        printf("%s\n", environ[i]);
    }
    return 0;
}

上面两种方法的最后的输出结果都是一样的,自行测试。

2-8 通过系统调用获取或设置环境变量

putenv 与 getenv

显然,putenv是设置/修改环境变量, getenv是获取环境变量的值。

我们来用一下getenv。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    printf("%s\n", getenv("PATH"));
    return 0;
}

输出:

此时,就有个比较好玩的,我们要写一个只允许在自己执行的程序该怎么写呢?可结合2-4 认识更多的环境变量,有兴趣可自行实现。

2-9 环境变量通常是具有全局属性的

变量通常具有全局属性,可以被子进程继承下去

我们可以用代码来测试一下:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    char *env = getenv("MYENV");
    if (env)
    {
        printf("%s\n", env);
    }
    else
    {
        printf("env:NULL\n");
    }
    return 0;
}

此时,我bash的环境变量中并没有MYENV环境变量,执行代码看结果:

我们在bash执行export MYENV=helloworld后直接执行程序:

我并没有重新编译程序,况且与重新编译程序无关,我们可以得到子进程确实继承了环境变量。

2-10 环境变量的来源

那我们最初的环境变量来自哪里呢?

答案是:配置文件。~/.bash_profile与 ~/.bashrc(不同系统略有差别 ,我这里使用的是CentOS7)。

每个用户的家目录下,都存在 .bash_profile与.bashrc。

我们来看下里面的主要内容吧。

shell 复制代码
# .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

~/.bash_profile会加载~/.bashrc

shell 复制代码
#bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
   . /etc/bashrc
fi
# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=
# User specific aliases and functions
alias vim='/home/lin/.VimForCpp/nvim'
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/.VimForCpp/vim/bundle/YCM.so/e17.x86_64

~/.bashrc会加载/etc/bashrc

所以,我们最终得到的结论就是环境变量是由配置文件加载而来的。

那就有个小问题,如果今天我们有10个用户登录会有多少张环境变量表?

10个,因为每个用户登录的时候,系统会为每个用户分配一个bash,每个bash都有自己对应的环境变量表。

注:

export是内建命令,不需要创建子进程,而是让bash自己亲自执行,bash自己调用函数,或者系统调用完成!

正因如此,export才能修改bash自身环境变量。


如果本文对您有启发:

点赞 - 让更多人看到这篇硬核技术解析 !

收藏 - 实战代码随时复现

关注 - 获取Linux系列深度更新
您的每一个[三连]都是我们持续创作的动力!✨

相关推荐
vin_zheng42 分钟前
破解企业安全软件网络拦截实战记录
运维
林姜泽樾2 小时前
Linux入门第十二章,创建用户、用户组、主组附加组等相关知识详解
linux·运维·服务器·centos
xiaokangzhe3 小时前
Linux系统安全
linux·运维·系统安全
feng一样的男子3 小时前
NFS 扩展属性 (xattr) 提示操作不支持解决方案
linux·go
南棱笑笑生3 小时前
20260310在瑞芯微原厂RK3576的Android14查看系统休眠时间
服务器·网络·数据库·rockchip
xiaokangzhe3 小时前
Nginx核心功能
运维·nginx
松果1773 小时前
以本地时钟为源的时间服务器
运维·chrony·时间服务器
Don.TIk3 小时前
SpringCloud学习笔记
笔记·学习·spring cloud
red_redemption3 小时前
自由学习记录(131)
学习
XDHCOM3 小时前
ORA-32152报错咋整啊,数据库操作遇到null number问题远程帮忙修复
服务器·数据库·oracle