学习目标:从认识Linux命令行基础开始,一直到写出自己的shell脚本
要想理解shell,需要先理解CLI。shell不单单是一种CLI,它是一个时刻都在运行的复杂式交互程序。
系统启动什么样的shell程序取决于用户个人ID的配置,在/etc/passwd文件中。此处以bash shell为例,bash shell程序位于/bin目录内。它是一个可执行程序。
默认的交互shell会在用户登录某个虚拟控制台中断或在GUI中运行终端仿真器时启动。不过还有另外一个默认的shell是/bin/sh,它作为默认的系统shell,用于那些需要在启动时使用的系统shell脚本。
除此之外,还有dash、tcsh、csh等其他shell。
用于登录某个虚拟控制器中断或在GUI中运行终端仿真器时所启动的默认的交互shell,是一个父shell。在CLI提示符后输入/bin/bash命令或其他等效的bash命令时,会创建一个新的shell程序。这个shell程序被称为子shell。子shell也拥有CLI提示符,同样会等待命令输入。
以下就运行了两个shell进程,输入/bin/sh命令后,一个子shell命令就出现了;而ps -f命令就是在子shell中运行的。父shell进程2287,子shell进程5661,进程的详细信息中的PID和PPID显示了它们之间的关系。但需要注意的是,在生成子shell进程时,只有部分父进程的环境会被复制到子shell环境中。
子shell可以从父shell中创建,也可以从另一个子shell中创建,使用ps --forest命令可以展示这些子shell间的嵌套关系。
可以在一行中指定要一次运行的一系列命令,可以通过命令列表来实现,命令你列表就是在需要指定的命令间加上分号(;)。但如果命令列表要想成为进程列表,需要加上括号()
尽管多出来的括号看起来没有什么太大的不同,但是起到的效果确实非同一般。括号的加入使命令列表变成了进程列表,生成了一个子shell来执行对应的命令
进程列表是一种命令分组。另一种命令分组是将命令放入到花括号中,并在命令列表尾部加上分号(;)。语法为{command; }。但是使用花括号进行命令分组并不会像进程列表那样创建出子shell。
要想知道是否生成了子shell,可以使用环境变量 $BASH_SUBSHELL,如果该值返回0,就表示没有子shell。如果返回1或者更大的数字,就表明存在子shell。
在shell脚本中,经常使用子shell进行多进程处理。但是采用子shell的成本不菲,会明显拖慢处理速度。在交互式的CLI shell会话中,子shell同样存在问题。它并非真正的多进程处理,因为终端控制着子shell的I/O。
后台模式
在后台模式中运行命令可以在处理命令的同时让出CLI,以供他用。演示后台模式的一个经典命令就是sleep。sleep命令接受一个参数,该参数是你希望进程等待(睡眠)的秒数。这个命令在脚本中常用于引入一段时间的暂停。命令sleep 10会将会话暂停10秒钟,然后返回shell CLI提示符。要想将命令置入后台模式,可以在命令末尾加上字符&
当一个命令置入后台后,会出现两条信息,第一条信息是显示在方括号中的后台作业号,一个是后台作业的进程ID。
除了ps命令,也可以使用jobs命令来显示后台作业信息。jobs命令可以显示出当前运行在后台模式中的所有用户的进程。
进程列表置入后台
在CLI中运用子shell的创造性方法之一就是将进程列表置入后台模式。这样既可以在后台处理繁重的工作,也不会让子shell的I/O受制于终端。比较典型的例子是使用tar命令。
(tar -cf Rich.tar /home/rich ; tar -cf My.tar /home/pool)&
协程
协程可以同时做两件事。它在后台生成一个子shell,并在这个子shell中执行命令,要使用协程处理,得使用coproc命令,还有要在子shell中执行的命令。
除了会创建子shell之外,协程基本上就是将命令置入后台模式。当输入coproc命令及其参数之后,就会发现启用一个后台作业,屏幕上会显示出后台作业号以及进程ID。
jobs命令能够显示出协程的处理状态。
将协程与进程列表结合起来可以产生嵌套的子shell。只需要输入进程列表,然后把命令coproc放在前面就行了。但是生成子shell的成本不低,而且速度还慢。创建嵌套子shell更会有不好的影响。
外部命令
外部命令,有时候也被称为文件系统命令,是存在于bash shell之外的程序。他们并不是shell程序的一部分。外部命令程序通常位于/bin、/usr/bin、/sbin或者/usr/sbin中。
ps命令就是一个外部命令,可以通过which和type命令找到
当外部命令执行时,会创建一个子进程。这种操作被称为“衍生”。外部命令ps很方便的显示出它的父进程以及自己所对应的衍生子进程。当进程必须执行衍生操作时,它需要花费时间和精力来设置子进程的环境。所以说,外部命令多少还是有代价的。
内建命令
内建命令和外部命令的区别在于前者不需要使用子进程来执行。它们已经和shell编译成一体,作为shell工具的组成部分存在。不需要借助外部程序文件来运行。因此内建命令的执行速度要更快、效率更高。
但是需要注意的是,有些命令有多种实现。例如echo和pwd既有内建命令也有外部命令,二者略有不同。
命令type -a可以显示出每个命令的两种实现。但是which命令只会显示外部命令文件。
对于多种实现的命令,如果想要使用其外部命令实现,直接指明对应的文件就行,比如/bin/pwd
bash shell用一个叫作环境变量的特性来存储有关shell会话和工作环境的信息,环境变量分成两类
全局变量
全部环境变量对于shell会话和所有生成的子shell都是可见的。系统环境变量基本上都是使用全大写字母,以区别于普通用户的环境变量。
使用env或printenv命令可以打印出环境变量
局部变量
局部环境变量只对创建它们的shell可见。
使用set命令会显示出全局变量、局部变量以及用户定义变量。它还会按照字母顺序对结果进行排序
设置用户变量
按照bash shell的标准惯例,如果是自己创建的局部变量或者是shell脚本,应该使用小写字母。避免重定义系统环境变量。
局部用户定义变量:仅在当前shell中可用
全局用户定义变量:在设定全局环境变量的进程所创建的子进程中,该变量都是可见的。创建全局环境变量的方法是先创建一个局部环境变量,然后再把它导出到全局环境中。这个过程通过export命令来完成,变量名前不需要加$
删除环境变量:使用unset命令即可完成这个操作
在设计环境变量名时,什么时候需要使用 , 什 么 时 候 不 需 要 ? 如 果 要 使 用 变 量 , 使 用 , 什么时候不需要? 如果要使用变量,使用 ,什么时候不需要?如果要使用变量,使用,如果要操作变量,不使用$
bash shell源自Unix Bourne shell,因此也保留了Unix Bourne shell里面定义的那些环境变量,常用的如下:
变量 | 描述 |
---|---|
CDPATH | 冒号分割的目录列表,作为cd命令的搜索路径 |
HOME | 当前用户的主目录 |
IFS | shell用来将文本字符串分割成字段的一系列字符 |
OPTARG | getopts命令处理的最后一个选项参数值 |
OPTIND | getopts命令处理的最后一个选项参数的索引号 |
PATH | shell查找命令的目录列表,由冒号分割 |
PS1 | shell命令行界面的主提示符 |
PS2 | shell命令行界面的次提示符 |
除了默认的Bourne的环境变量,bash shell还提供一些自由的变量。
变量 | 描述 |
---|---|
BASH | 当前shell实例的全路径名 |
ENV | 用户配置的环境变量 |
LANG | shell的语言环境类别 |
LC_ALL | 定义一个语言环境类别,能够覆盖LANG变量 |
LCD_COLOATE | 设置对字符串排序时用的排序规则 |
LC_CTYPE | 决定如何解释出现在文件名扩展和模式匹配中的字符 |
LC_NUMERIC | 决定着格式化数字时采用的语言环境设置 |
PWD | 当前工作目录 |
SHELL | bash shell的全路径名 |
UID | 当前用户的真实用户ID(数字形式) |
在登入Linux系统启动一个bash shell时,默认情况下bash会在几个文件中查找命令,这些文件叫作启动文件或环境文件。bash检查的启动文件取决于启动bash shell的方式。
登录shell
bash shell作为登录shell启动会从以下5个不同的启动文件里读取命令
/etc/profile: 系统上默认的bash shell的主启动文件,每个用户登录时都会执行
$HOME/.bin_profile
$HOME/.bashrc
$HOME/.bash_login
$HOME/.profile
交互式shell
交互式shell启动时不会访问/etc/profile文件,只会检查用户HOME目录下的.bashrc文件
.bashrc文件有两个作用:一是查看/etc目录下通用的bashrc文件,二是为用户提供一个定制自己的命令别名和私有脚本函数的地方
非交互式shell
系统执行shell脚本时用的就是这种shell。不同的地方在于它没有命令行提示符。但是在系统上运行脚本时,也许希望能够运行一些特定启动的命令。为了处理这种情况,bash shell提供了BASH_ENV环境变量。当shell启动一个非交互式shell进程时,它会检查这个环境变量来查看要执行的启动文件。如果有指定的文件,shell会执行改文件里的命令,这通常包括shell脚本变量设置。
环境变量有一个很好的特性就是,它们可以作为数组使用。数组是能够存储多个值的变量,这些值可以单独引用,也可以作为整个数组来引用。
环境变量的索引值都是从零开始。
mytest=(one two three four five)
echo $mytest
one
echo $(mytest[2])
three
echo $(mytest[*])
one two three four five
全局环境变量可以在对其作出定义的父进程所创建的子进程中使用。局部环境变量只能在定义他们的进程中使用。
Linux系统使用全局环境变量和局部换将变量存储系统环境信息。可以通过shell的命令行界面或者在shell脚本中访问这些信息。PATH环境变量定义了bash shell在查找可执行可执行命令时的搜索目录。可以修改PATH环境变量来添加自己的搜索目录,以方便程序的运行。
也可以创建自用的全局和局部环境变量。一旦创造了环境变量,它在整个shell会话中就都是可用的。
bash shell会在启动时执行几个启动文件。这些启动文件包含了环境变量的定义,可用于为每个bash会话设置标准环境变量。每次登录Linux系统,bash shell都会访问/etc/profile启动文件以及3个针对每个用户的本地启动文件:HOME/.bash_profile、HOME/.bash_login和 HOME/.profile 用户可以在这些文件中定制自己想要的环境变量和启动脚本。