1.
我们平常所用的Linux指令其实也是可执行程序,和我们自己写的二进制程序没什么两样,那么为什么在执行自己的程序的时候需要加上./,而执行这些系统提供的指令(可执行程序),不需要加上./呢?
2.
要执行一个程序或者指令,必须先找到这个程序。
这也就是为什么我们在执行自己写的程序时,要加上./(当前路径),这其实就是为了找到我们所写的程序,而系统指令实际上是系统默认帮我们找到其程序的位置并且执行,如果想不带./的执行自己的程序,那就需要将程序安装(安装的本质就是拷贝)到/usr/bin目录下面,也就是系统安装指令的路径当中。
但不建议将自己的程序安装到系统默认路径下,因为你的程序是未经过严格测试的,会污染系统的指令池的,所以如果你安装了的话,建议玩完儿之后,再把它删了吧!
1.
为什么在/usr/bin路径下的程序,系统就可以找到呢?其实是因为系统里面存在环境变量PATH,操作系统在启动的时候,会在shell的上下文当中定义一个PATH变量,这个变量是全局有效的,如果想要查看内容,可以利用echo命令并且在PATH前面要加$符号。
2.
在执行系统指令的时候,系统会默认去PATH环境变量里面的路径,查找我们输入的指令程序,如果找到,系统就会执行这个程序,如果没有找到就会报错command not found,所以如果想要不带./执行自己的程序还有一种办法就是,将自己所写的程序所在的路径添加到环境变量PATH里面。
我们可以任意修改PATH的值,因为只要重新退出登录,PATH环境变量就又会恢复了
3.
export可以用来将shell变量导入到环境变量PATH里面,导入的时候需要先将老的环境变量导入进去然后在加上新的路径,否则会出现你的路径直接覆盖掉之前环境变量PATH里面的所有路径的情况,PATH里面的路径下的所有程序都被默认为是系统指令
4.
which指令在底层实现上实际就是从环境变量PATH下的路径当中搜索的,帮助我们查找对应的指令路径,如果我们的程序所在路径也被添加到了PATH中,那么which也就可以查到我们所写的程序了。
5.
我们的环境变量是内存级的,所以在你将自己的路径导入到环境变量PATH之后也只是暂时的,等你退出xshell之后,你的环境变量就又会恢复到默认的样子了
1.
在我们登录shell的时候,会默认让你当前的shell进程,把对应的bash_profile里面的内容执行一次,就是将环境变量导入到你当前的shell进程当中,环境变量的配置就是通过它在启动的时候加载到bash当中的,linux在环境变量的配置文件当中就有环境变量的设置,当我们登录shell的时候这个环境变量就会load到当前的shell进程当中。
2.
shell做为一种和Linux系统的特殊交互式工具,为用户提供了启动程序、管理文件系统中的文件及运行在Linux上的进程的途径。shell通过解析输入的文本命令,在内核中执行来达到与系统交互的功能。shell包含了一组内部命令,通过这些命令可以进行文件管理、程序管理及运行等操作。
3.
除了在文本命令界面上(or虚拟控制器终端or终端仿真器)通过命令行执行外,可以通过将多个shell命令放入文件中作为程序执行,这些文件就是shell脚本。在Linux系统中有许多不同类型的shell(如ash、tsch、zsh等),它们各自有不同的特性,可以根据需求自行选择。通常Linux发行版本的默认shell都是bash shell(由GNU项目开发的类Unix shell)。
4.
通常我们在执行shell命令的时候直观的感觉就是命令是直接运行在Linux系统上的,其实这是个主观的误解,shell本身就是个程序是运行在Linux上的进程,shell命令的执行是在对应的进程内运行的
5.
当用户登入到虚拟终端or终端仿真器上时,就会启动默认的shell程序。用户登入启动什么样的shell取决于在/etc/passwd用户配置文件中列出的用户默认shell。
6.
Linux系统本身也有一个默认的shell就是/bin/sh,是用于在系统中启动系统shell脚本所指定的默认shell。通常在Linux系统中这个文件是一个符号链接文件,指向/bin/bash这个shell,也可以更改/bin/sh的链接来更换系统默认shell
7.
用户登入终端所启动的shell是一个父shell。在终端的提示符后输入bash命令或其他等效bash命令时会创建一个新的shell程序,这个shell被称为子shell。如下,我们在终端中输入两次bash命令后使用ps --forest查看进程嵌套关系可以看出父shell和子shell的关系
本文第三部分shell进程的部分内容转载自csdn博主ONLY_MIT的文章
1.
无论是我们自己写的程序还是操作系统提前给我们准备好的程序,想要运行都必须先加载到内存里面,因为CPU只能从内存中读取代码和数据,但是这里有一个潜在的问题,这些程序想要运行,都必须让操作系统先找到这些程序,找到这些程序才能把他们加载到内存里面。
2.
操作系统要找这些程序就必须去特定的路径下面去找这些程序,包括系统自带的指令程序和我们自己所写的程序,操作系统还有可能找头文件,找动态库静态库等等,但是为什么你说操作系统能找到这些东西,它就能一定找到呢?操作系统如何找到这些东西啊?
3.
其实想要找到这些东西,操作系统需要做很多的准备工作,才能像我们所说的那样轻松,想要找什么东西就把那个东西找到了,所以操作系统在启动的时候,他就已经默认从配置文件当中读取了他自己曾经把软件安装到了哪些路径下,他把安装到哪些路径下这些重要信息都记录在配置文件里面,等到OS启动的时候,把配置文件中的这些信息导入到内存里面,构建出一个内存级变量,这种变量就是环境变量,上面所讲的PATH环境变量就是操作系统在启动命令行解释器shell的时候,将PATH这样的变量导入到shell的上下文当中,当我们执行对应的指令的时候,我们就必须通过PATH环境变量里面指定的默认的搜索路径去查找对应的可执行程序,所以操作系统为了让我们找到可执行程序,其实做了很多的准备工作,帮我们定义了许许多多的环境变量,通过环境变量帮助我们做了很多本身我们看不见的工作,除PATH这个环境变量,其实还有很多很多的其他环境变量,操作系统需要完成其他很多我们忽略掉的工作,这就需要依靠这些它自己定义出来的环境变量,这些环境变量都有不同的用途,配色方案,当前路径,主机名,用户名,历史指令记录,默认的shell类型这些都要依靠OS他自己定义出来的环境变量去隐式的解决或处理我们看不到的问题和工作。
4.
在不同的使用场景下,要求操作系统在启动shell之后,给我们做命令行解释的时候,必须预先设置好一批未来shell可能用到的变量,通过这些变量完成我们输入的命令的解释,所以操作系统为了满足不同的应用场景,必须预先在自己的OS内设置一大批的全局变量,这些全局变量其实就是环境变量!
1.
su - 其实是让root重新登录,这时候就会以root的身份重新加载很多的东西,把root相关的环境变量全部加载到shell的上下文当中,su仅仅是将身份切换一下,但是当前的路径是没有变化的。
2.
可以看到环境变量USER会随着我们身份的切换不断的更新它的变量值,这也正是USER环境变量所能起到的作用。
1.
HOME环境变量记录当前用户的工作目录在具体的哪个路径。
2.
cd的本质其实就是shell在解析指令时,看到了波浪号,shell就会直接调用环境变量HOME的值
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #define USER "USER"
5 #define MY_ENV "myval"
6
7
8 int main()
9 {
10 char*myenv=getenv(MY_ENV);
11 if(NULL == myenv)
12 {
13 printf("%s: not found\n",MY_ENV);
14 return 1;
15 }
16 printf("%s=%s\n",MY_ENV,myenv);
17
18 return 0;
19 }
1.
父进程shell定义的本地变量不会被子进程继承下去,但是父进程的环境变量是会被子进程继承下去的,继承的原因就是为了满足不同的应用场景,因为许多系统指令(ls、whoami、pwd、which、su - )都会涉及到使用环境变量,所以这些指令(子进程)必须继承父进程bash的环境变量,以满足不同的使用场景。
所以环境变量是具有全局性的,因为无论是父进程还是子进程都有环境变量,子进程的环境变量是从父进程继承得来的。
2.
本地变量只会在当前进程bash内部有效,因为它不会被继承下去,具有局部性。
1.
set指令可以显示shell中的环境变量和非环境变量
2.
set显示出来的变量巨多,因为shell本地变量包括了环境变量
export MYVAL="youcanseeme"
unset MYVAL
1.
导入环境变量可以先在命令行定义本地变量,然后导成环境变量,也可以两个步骤同时进行,取消环境变量或本地变量可以通过unset指令来操作。
2.
由于自己定义的环境变量默认就是字符串,所以在定义的时候既可以带上双引号,也可以不带双引号,但如果出现定义的环境变量带空格的话,就必须带上双引号了,所以还是建议在定义的时候带上空格
1.
shell会维护环境变量的值,就比如我们会不停的切换路径,那么环境变量PWD的值就会随时被shell更改为当前路径,所以ls在作为子进程运行的时候,继承PWD的值之后,ls就可以理所应当的显示出来当前的路径是在哪里。
2.
下面我们也可以自己用C语言程序的运行来获取当前路径,利用shell实时维护环境变量PWD的特性来完成。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #define USER "USER"
5 #define MY_ENV "myval"
6 #define MYPWD "PWD"
7
8 int main()
9 {
10 printf("%s\n",getenv(MYPWD));
11 }
1.
在main函数中实际上有隐藏的参数,只不过我们平常不使用这些参数,因为我们平常用不着,在系统编程中,使用这些参数是比较常见的,并且main会被一个叫START_UP的函数调用,START_UP函数在你的程序加载到内存的时候,被操作系统调用,参数实际上就是我们的命令行解释器bash也就是父进程传递的,我们只需要在命令行上写指令就可以了,shell在解释我们的指令时,就会给main函数传参了。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #define USER "USER"
5 #define MY_ENV "myval"
6 #define MYPWD "PWD"
7 #include <unistd.h>
8
9
10
11 int main(int argc ,char* argv[])
12 {
13
14 for(int i=0;i<argc;i++)
15 {
16 printf("argv[%d]->%s\n",i,argv[i]);
17 }
18
19 }
2.
在命令行中运行程序的时候,实际上可以添加命令行参数,shell在解释这些指令的时候,就会给main函数传参
3.
main函数中的第一个参数是命令行中运行程序的时候字符串的个数,以空格为分隔符,比如上面运行时-a -b -c等,实际上是三个字符串,./mycmd也是一个字符串,所以argc代表的就是字符串的个数,argv指针数组中的指针,指向的就是这些字符串,通过程序运行结果和代码,可以证明这个结论,argv数组中打印出来的值实际上就是这些字符串,所以main函数中的第二个参数就是命令行参数表,表中的指针指向命令行中的所有字符串。
那么他有什么意义呢?
4.
系统指令其实就是C语言写的程序,那么它在带指令运行的时候,也能实现不同的功能,这是怎么做到的呢?实际上在实现的代码中的main函数就是需要argc、argv这样的参数实现的
下面我们就通俗的实现一个不同选项拥有不同功能的进程。
int main(int argc ,char* argv[])
12 {
13
14 if(strcmp("-a",argv[1]) == 0)
15 {
16 printf("功能a\n");
17 }
18
19 if(strcmp("-b",argv[1]) == 0)
20 {
21 printf("功能b\n");
22 }
23 if(strcmp("-c",argv[1]) == 0)
24 {
25 printf("功能c\n");
26 }
27
28 if(strcmp("-ac",argv[1]) == 0)
29 {
30 printf("功能ac\n");
31 }
32 if(strcmp("-bc",argv[1]) == 0)
33 {
34 printf("功能bc\n");
35 }
36 if(strcmp("-ab",argv[1]) == 0)
37 {
38 printf("功能ab\n");
39 }
40 if(strcmp("-abc",argv[1]) == 0)
41 {
42 printf("功能abc\n");
43 }
44 }
5.所以命令行参数最大的意义就是,通过不同的参数(也就是执行时携带的选项)使得进程拥有不同的功能。
6.
在windows下的命令提示符当中,我们也可以通过不同的命令行参数,来使得进程实现不同的功能,例如下面的关机指令,可以设置关机时间,也可以取消关机,选择关机,通过-t、-a、-s等参数实现。
1.
通过getenv( )系统调用就可以获得环境变量USER的值是什么
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #define USER "USER"
5
6
7
8 int main()
9 {
W> 10 char*who = getenv(USER);
11 printf("USER:%s\n",who);
12 return 0;
13 }
2.
sudo的本质其实就是将环境变量USER由普通用户改成root用户,这时候某些不允许普通用户所做的行为,通过root用户的身份就可以做了。
下面代码以通俗的方式解释了我们平常以普通身份进行某些操作时,遇到的权限拒绝的原理,其实在真正的过程当中是需要用到getenv()和一些文件属性获取的系统调用stat(),通过这些系统调用,来查看你是否拥有对应操作的权限。
3.stat获取文件属性,这一系统调用接口,后面的博文会详细讲解。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #define USER "USER"
5
6
7
8 int main()
9 {
10 char*who = getenv(USER);
11 if(strcmp(who,"root")==0)
12 {
13 printf("USER:%s\n",who);
14
15 }
16 else
17 {
18 printf("permission denied!\n");
19 }
20
21 return 0;
22 }
W> 11 int main(int argc ,char* argv[],char* env[])
12 {
13
14 for(int i=0; env[i]; i++)
15 {
16 printf("env[%d]-->%s\n",i,env[i]);
17 }
18
19 }
1.
可以看到,通过main函数的第三个参数,子进程也可以获得所有的环境变量。这也是子进程继承shell环境变量的一种方式。
2.
每个进程都会被shell传一个环境表,环境表是一个字符指针数组,每个指针指向一个以斜杠0结尾的环境变量字符串
1.
C语言默认提供了一个第三方指针变量叫做environ,在调用main的时候,实际上系统就把environ这个变量作为main的第三个参数传给main函数了,这里涉及到C语言中数组传参的问题,environ就相当于指针数组env[ ]的另一个数组名,environ指向的就是env数组的第一个元素,根据数组名代表首元素地址可知,environ其实就是env的另一个数组名。
2.
environ是在编译好c或c++程序之后,系统默认初始化好的一个全局的二级指针,指向env数组的第一个元素,因为environ[ i ]=*( environ + i ),所以另一种获取环境变量的方式如下
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5
6 int main()
7 {
8
9 extern char** environ;
10 for(int i=0; environ[i]; i++)
11 {
12 printf("environ[%d]-->%s\n",i,environ[i]);
13 }
14
15 }
前面是通过命令行式的export指令来增加环境变量,我们也可以通过系统调用putenv()来更改或增加环境变量,这个系统调用放到后面的博文来进行详细的讲解。