【把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系列深度更新
您的每一个[三连]都是我们持续创作的动力!✨

相关推荐
im_AMBER5 小时前
Leetcode 110 奇偶链表
数据结构·学习·算法·leetcode
lcx_defender6 小时前
【Docker】Docker部署运行Seata
运维·docker·容器
cuijiecheng20186 小时前
Linux下inih库的使用
linux·运维·服务器
GIS瞧葩菜6 小时前
entity几何体轴编辑(沿 Z 轴平移)完整流程拆解
linux·运维·ubuntu
大雷神7 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地-- 第24篇:学习中心 - 课程体系设计
大数据·学习·harmonyos
confiself8 小时前
GO环境配置
linux·运维·centos
爱装代码的小瓶子8 小时前
【c++与Linux基础】文件篇(4)虚拟文件系统VFS
linux·开发语言·c++
可可嘻嘻大老虎14 小时前
nginx无法访问后端服务问题
运维·nginx
小白郭莫搞科技14 小时前
鸿蒙跨端框架Flutter学习:CustomTween自定义Tween详解
学习·flutter·harmonyos