当用户登陆Linux就启动了一个Shell,多数Linux默认为bash。取得一个bash之后,当前bash就是一个独立的进程。接下来在这个bash下执行的任何命令都是由这个bash衍生出来的,被执行的命令被称为子进程。

Bash执行脚本的时候,会创建一个新的Shell,这个Shell就是脚本的执行环境,Bash默认给定了这个环境的各种参数,bash使用一个叫环境变量的特性来存储有关shell会话和工作环境的信息。

进程

进程是程序执行是的上下文集合(实例),这个集合包括程序代码、数据段、堆栈、环境变量、内核标识进程的数据结构等,进程能够分配给cpu和内存等资源。进程一般包括指令集和系统资源,其中指令集就是你的代码,系统资源就是指cpu、内存以及I/O等。

一个进程可以生成另一个进程,生成的进程称为子进程,在unix系统中进程通过依次调用fork()exec()系统调用来实现创建一个子进程。

fork就是在内存中将当前进程的所有内存镜像复制一份,所有东西都一样,包括进程运行到哪句代码,只修改新进程的进程号(PID),所以新的进程会继续执行fork()后面的代码,父进程也会运行fork()后面的代码,从fork()开始父子进程就是两个独立的进程,独立的内存空间。

exec是一组函数的统称,用磁盘上的一个新的程序替换当前的进程的正文段、数据段、堆栈段。exec并不产生新的进程,而是替换当前进程。进程将从新代码的main开始执行,相当于另外运行了一个完全不同的程序,但保留了原来环境变量。

在命令行中执行一个shell脚本时,shell进程会创建一个sub-shell子进程来执行这个shell脚本,并等待这个子进程执行结束。

在命令行中执行一个命令时,shell这个进程会通过fork和exec为我们创建一个子进程,并等待(waitpid)这个子进程完成退出。

bash

在原本的bash底下运行另一个bash,操作的环境接口会跑到第二个bash去(就是子进程),原本的bash就会暂停。若要回到原本的bash去,需要将第二个bash结束掉 (exit 或 logout)。

有一小部分命令执行时不会创建新的子进程,称为build-in命令,如setsourceexport

线程是进程的一个执行流,线程不能分配系统资源,它是进程的一部分,比进程更小的独立运行的单位。

环境变量

环境变量是一组特殊的字符型变量,由于具有继承性质,环境变量也经常用于父子进程传递参数用。

通过declare命令以变量名=值设置自定义变量,父进程的自定义变量是无法被子进程继承,如果要在子进程使用,则需要用export将其提升为环境变量。

进程可通过setenvputenv更改自己的环境变量,环境变量的继承只能单向,即从父进程继承给fork出来的子进程。

使用printenvenv命令可以查看环境变量,使用printenv HOMEecho $HOME 查看指定环境变量,使用unset删除变量。

当把自定义变量提升为环境变量后,对于当前进程及所有子进程都是可用的,但无法在其他进程。如果想要在所有shell会话中使用,需要将环境变量“持久化”,将配置写入配置文件,比如/etc/profile/.bashrc等,参考文件加载

环境变量的数据可以被子进程引用是因为内存配置的关系:

  1. 当启动一个shell,操作系统会分配一内存区块给shell使用,此内存内之变量可让子进程取用
  2. 在父进程利用export功能,让自定义变量的内容写到上述的内存区块当中(环境变量)
  3. 当加载另一个shell时 (启动子进程),子进程复制父进程的内存区到当前进程

export

export命令通过调用putenv将一个本地变量输出为当前shell的环境变量,从而被子进程自动继承,但是子进程export的变量无法改变父进程的环境变量。

一般来说,配置交叉编译工具链的时候需要指定编译工具的路径,此时就需要设置环境变量。

当不带任何参数时表示查看已经存在的环境变量。

  • -f:代表变量名称中为函数名称;
  • -n:删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中;
  • -p:列出所有的shell赋予程序的环境变量。
# 设置变量
$ name=ruesin
# 提升为环境变量
$ export name
# 进入子进程
$ bash
# 输出环境变量
$ echo $name

set

set命令用来修改shell环境的运行参数,也就是可以定制环境,可以设置shell变量的新变量值,但不能够定义新的shell变量。

set设置了当前shell进程的本地变量,本地变量只在当前shell的进程内有效,不会被子进程继承和传递。

set设置变量的值时,如果未指定值,则该变量值将被设为NULL。

使用set更改shell特性时,符号”+”和”-“的作用分别是打开和关闭指定的模式。

set命令不带参数时,用来显示系统中已经存在的shell变量,包括全局变量、局部变量、用户自定义变量。

  • -a:标示已修改的变量,以供输出至环境变量。
  • -b:使被中止的后台程序立刻回报执行状态。
  • -C:转向所产生的文件无法覆盖已存在的文件。
  • -d:Shell预设会用杂凑表记忆使用过的指令,以加速指令的执行。使用- -d参数可取消。
  • -e:若指令传回值不等于0,则立即退出shell。
  • -f:取消使用通配符。
  • -h:自动记录函数的所在位置。
  • -H Shell:可利用”!”加<指令编号>的方式来执行history中记录的指令。
  • -k:指令所给的参数都会被视为此指令的环境变量。
  • -l:记录for循环的变量名称。
  • -m:使用监视模式。
  • -n:只读取指令,而不实际执行。
  • -p:启动优先顺序模式。
  • -P:启动-P参数后,执行指令时,会以实际的文件或目录来取代符号连接。
  • -t:执行完随后的指令,即退出shell。
  • -u:当执行时使用到未定义过的变量,则显示错误信息。
  • -v:显示shell所读取的输入值。
  • -x:执行指令后,会先显示该指令及所下的参数。
# 使用declare命令定义一个新的环境变量:
declare name='ruesin'
# 使用set命令将变量输出为环境变量
set -a name
#显示环境变量值
env | grep name

source

source命令以一个脚本为参数,该脚本将作为当前shell的环境执行,即不会启动一个新的子进程,在当前bash环境下读取并执行FileName中的命令。

source命令也称为“点命令”,也就是一个点符号(.),source命令(从 C shell而来)是bash shell的内置命令。点命令.,(从Bourne Shell而来)是source的另一名称。

source FileName
. FileName

source命令通常用于重新执行刚修改的初始化文件,使之立即生效:

假如在登录后对.bash_profile中的TERM变量做了修改,则能够用source命令重新执行.bash_profile中的命令而不用注销并重新登录。

比如在一个脚本里export $KKK=111,假如用./a.sh执行该脚本,执行完毕后,运行echo $KKK,发现没有值,假如您用source来执行,然后再echo,就会发现KKK=111。因为调用./a.sh来执行shell是在一个子shell里运行的,所以执行后,结构并没有反应到父shell里,但是source不同他就是在本shell中执行的,所以能够看到结果。

env

env命令不带任何选项和参数时,用于显示系统中已存在的环境变量,以及在定义的环境中执行指令。该命令只使用”-“作为参数选项时,隐藏了选项”-i”的功能。

env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
  • -i:开始一个新的空的环境;
  • -u<变量名>:从当前环境中删除指定的变量。

env仅为将要执行的子进程设置环境变量,临时启用指定的环境变量,执行子进程,而不影响之后的进程。

env GOTRACEBACK=crash ./test.sh

env不是shell的build-in命令,所以shell执行env的时候需要创建子进程。

从本质上说,env相当于shell先fork,然后在子进程中运行env,子进程env调用execve运行test.sh时,多传了一个GOTRACEBACK=crash的环境变量,这样test.sh可以看到这个GOTRACEBACK环境变量,但由于没有调用putenv改变父shell的环境变量,所以后续启动的进程并不继承GOTRACEBACK。

exec

exec命令用于在当前进程中调用并执行指令的命令。exec命令通常用在shell脚本程序中,可以调用其他的命令。如果在当前终端中使用命令,则当指定的命令执行完毕后会立即退出终端。

exec运行脚本或命令的时候,不会启用一个新的shell进程,当前shell的代码执行到exec后,代码被替换成了exec要执行的程序。因为shell本身都被替换掉了,exec后续的脚本内容不会执行,即当前shell进程结束了。

文件加载

系统有一些环境配置文件的存在,让bash在启动时直接读取这些配置文件,以规划好bash的操作环境。而这些配置文件又可以分为全体系统的配置文件以及用户个人偏好配置文件。

login shell

取得bash时需要完整的登陆流程的,就称为login shell

比如要由tty1 ~ tty6登陆,需要输入用户的账号与密码,此时取得的bash就称为login shell

一般来说,login shell其实只会读取这两个配置文件:

  • /etc/profile:系统整体的配置,最好不要修改这个文件
  • ~/.bash_profile 或 ~/.bash_login 或 ~/.profile:属于使用者个人配置,可以在这里修改自己的数据

/etc/profile

这个配置文件是整体环境配置文件,可以利用使用者的标识符 (UID) 来决定很多重要的变量数据,是每个用户登陆取得bash时一定会读取的配置文件。如果想要帮所有用户配置整体环境,可以修改这个文件。

login shell读取的整体环境配置文件其实只有/etc/profile,此文件会加载其他的配置文件:

  • /etc/inputrc:如果用户没有自定义输入的按键功能,/etc/profile会配置INPUTRC=/etc/inputrc这个变量,此文件内容为bash的热键、[tab]要不要有声音等数据。
  • /etc/profile.d/*.sh:加载/etc/profile.d/目录下扩展名为.sh的所有文件,使用者需要具有r的权限。
  • /etc/sysconfig/i18n:由/etc/profile.d/lang.sh加载,是决定bash默认语言的配置文件,最重要的就是LANG这个变量。

~/.bash_profile

bash在读完了/etc/profile及其他配置文件后,接着读取使用者的个人配置文件。

login shellbash环境中,读取的个人偏好配置文件主要有三个:

  • ~/.bash_profile
  • ~/.bash_login
  • ~/.profile

bashlogin shell配置只会读取上面三个文件的其中一个,而读取的顺序则是依照上面的顺序。也就是说,如果~/.bash_profile存在,那么其他两个文件不论有无存在,都不会被读取。

login shell的读取流程:

login-shell

实线的的方向是主线流程,虚线的方向则是被呼叫的配置文件。

CentOSlogin shell环境下,最终被读取的配置文件是~/.bashrc,可以将自己的偏好配置写入该文件。

由于/etc/profile~/.bash_profile是在取得login shell时才会读取的配置文件,文件修改后,需要注销再登陆后配置才会生效。通常使用source /etc/profile命令执行文件使其立即生效。

non-login shell

不需要重复登陆取得bash的环境就称为non-login shell,比如:

  • X window登陆Linux后,再以X的图形化接口启动终端机,此时那个终端接口并没有需要再次的输入账号与密码
  • 在原本的bash环境下再次下达bash这个命令

~/.bashrc

取得non-login shell时,仅会读取~/.bashrc该配置文件,由于个人目录下,每个用户的~/.bashrc不一定相同。

修改~/.bashrc后记得source ~/.bashrc,使配置文件生效。

参考