一、认识bash这个shell
管理整个硬件的是操作系统的内核(kernel),内核是需要被保护的,所以一般使用者只能通过shell来和内核沟通,来让内核达成我们想要达成的工作。那么操作系统有多少shell,为什么我们要使用bash?下面回答下这些问题。
1.1 硬件、内核与shell
首先我们先了解下电脑的工作状态,以“我们的电脑需要输出一段音乐”为例,来看下我们的电脑需要哪些部分:
- 硬件:首先需要我们有“声卡”这个配置,不然无法输出声音
- 内核管理:我们需要操作系统的内核支持这个“硬件”,所以还需要提供这个“声卡”的驱动程序
- 应用程序:提供发出声音的指令
这就是我们输出声音所需要的步骤,也就是说,我们必须输入一个指令后,硬件才会通过我们下达的指令来工作。那么硬件如何知道我们所下达的指令呢?这就是通过内核来完成,这也就是说,我们必须要通过shell将我们输入的指令与内核沟通,让内核可以控制硬件来正确无误的工作。
操作系统本身也是一组软件,由于这组软件控制全体硬件,如果操作系统能被使用者随意的操作,那么稍有不当就会导致整个系统的崩溃。所以操作系统不能被一些没有管理能力的终端用户随意使用。
但是我们总是需要让使用者调用操作系统的一部分功能来完成特定的任务,所以就有了在操作系统上建立的程序。使用者可以通过这些程序来调用操作系统内核,让内核来完成我们所需要的硬件任务。如下图所示:
我们可以发现这些应用程序是在最外层,就如同鸡蛋的外壳一样,因此这些程序也就被程序壳程序(shell)
其实壳程序的功能只是提供使用者调用操作系统的一个接口,因此shell还需要可以呼叫其他程序。类似man、chmod、chown等程序,这些指令都是独立的应用程序,但是我们可以通过shell(就是指令列模式)来操作这些应用程序,让这些应用程序呼叫核心来完成所需的工作。
注意,只要能够操作应用程序的接口就可以被称为shell。狭义的shell指的是指令列方面的软件,包括bash等。广义的shell则包括图形界面的软件,因为图形界面其实也能够操作各种应用程序来呼叫内核进行工作。
1.2 系统的合法shell与/etc/shells功能
知道什么是shell后,接下来我们了解下Linux使用的是哪一个shell。由于早年的Unix年代版本众多,所以shell也有许多不同的版本,例如京城听到的Bourne Shell(sh)、在Sun里面预设的C Shell、商业上常用的K Shell、还有TCSH等等,每一个shell都有其特点。Linux使用的这一版本就称为Bourne Again Shell(简称bash),这个shell是Bourne Shell的增强版本,也是基于GNU的架构下发展出来的。
我们可以检查下/etc/shells这个文件来查看下我们的Linux目前为止有多少我们可以使用的shells,目前至少有以下几个可用的shell:
- /bin/sh (已经被 /bin/bash 所取代)
- /bin/bash (就是 Linux 预设的 shell)
- /bin/tcsh (整合 C Shell ,提供更多的功能)
- /bin/csh (已经被 /bin/tcsh 所取代)
系统某些服务在运行过程中,会去检查使用者能够使用的shells,而这些shell的查询就是借助/etc/shells这个文件。所以我们系统上合法的shell都会写入/etc/shells这个文件。举例来说,某些FTP网站会去检查使用者的可用shell,而如果我们不想要这些使用者使用FTP以外的主机资源时,就会给予该使用者一个奇怪的shell,让使用者无法以其他服务登入主机。这个时候,我们就需要将这些奇怪的shell写入到/etc/shells当中了。举例来说,我们的/etc/shells里面就有一个/sbin/nologin文件的存在,这就是我们所说的奇怪的shell。
当我们登陆一个Linux系统时,系统就会分配一个shell给我们进行工作,而这个shell就记录在/etc/passwd文件内。
1.3 bash shell的功能
bash是GNU计划中重要的工具软件之一,目前也是Linux发行版的标准shell。bash兼容sh,并且依据一些使用者需求进行了加强。无论我们使用哪种Linux发行版,我们都需要学习bash。bash的优点主要有以下几个:
命令记忆功能(history)
只要我们在指令列中按上下键,就可以找到前/后一个输入的指令。而在很多发行版里面,预设的指令记忆功能可以达到1000个,也就是说,我们曾经下达过的指令几乎都被记录下来了。
这些指令都记录在家目录里面的.bash_history里面。不过,需要留意的是,./bash_history里面记录的是前一次登陆以前所执行过的指令,而至于这一次登陆所执行的指令都保存在内存中,当我们成功的退出系统后,这些指令才会被记录在.bash_history中。
命令与文件补全功能(tab键)
tab的使用方法如下:
- tab接在一串指令的第一个字的后面,则为命令补全
- tab接在一串指令的第二个字以后时,则为文件补全
- 如果安装bash-completion软件,则在某些指令后使用tab键时,可以进行选项/参数的补齐功能
命令别名设定
假如我们需要知道这个目录下的所有文件(包括隐藏文件)及所有的文件属性,那么我们就必须下达ls -al
这样的指令串。其实我们可以使用别名来简化这个过程,在指令列输入alias lm='ls -al'
,以后就可以使用lm来实现这个过程了
工作控制、前台与后台
使用前、后台的控制可以让工作进行的更为顺利,而工作控制的用途更为广泛,可以让我们随时将工作丢到后台去进行。而不怕不小心使用了ctrl+c来停止了程序。此外,还可以在单一登陆的场景中,达到多工的目的。
shell脚本
在Linux下的shell脚本可以让我们将平时管理系统常需要下达的连续指令写成一个文件。
万用字符
除了完整的字符串外,bash还支持许多万用字符来帮助使用者来查询与指令下达。举例来说,想要知道/usr/bin下面有多少以X为开头的文件,使用ls -l /usr/bin/X*
就可以知道,此外,还有其他可供利用的万用字符,这些都可以加快使用者的操作。
1.4 查询指令是否是bash shell的内置命令:type
/待补充
1.5 指令的下达与快速编辑
/待补充
二、shell的变量功能
2.1 变量的基本介绍
变量就是以一组文字或符号等来代表不固定的内容。
变量的可变性与方便性
举例来说,我们每个账号的邮件信箱是以MAIL这个变量来进行存取的,当dmtsai这个使用者登入时,他便会取得MAIL这个变量,而这个变量的内容其实就是/var/spool/mail/dmtsai,如果另外一个使用者vbird登入时,他取得的这个变量的内容就会变成/var/spool/mail/vbird。当我们使用邮件读取指令mail来读取自己的邮件信箱时,这个程序就可以直接读取MAIL这个变量的内容,自动分辨出属于自己的信箱邮件。
如上图所示,因为系统已经帮我们规划好了MAIL这个变量,使用者只需要知道mail这个指令如何使用即可,mail会主动的取用MAIL这个变量,就能够像上图所示的取得自己的邮件了。
影响bash环境操作的变量
某些特定变量会影响到bash的环境,以PATH变量为例,我们可以在任何目录下执行某个指令,这与PATH变量有很大的关系。例如我们下达ls这个指令时,系统就是透过PATH这个变量里面的内容所记录的路径顺序来搜索指令。当我们搜索完PATH这个变量内的路径还找不到ls指令时,就会在屏幕上显示command not found
的错误信息了
更具体的来说,我们在真正的用shell来和Linux沟通,是在正确的登入Linux后,这个时候我们需要有一个bash的执行程序,才可以真正的经由bash来和系统沟通。而在进入shell前,由于系统需要一些变量来提供资料的存取(或者是一些环境的设定参数值,例如是否要显示彩色的),所以就有了一些所谓的环境变量需要来读入系统中。这些环境变量比如PATH、HOME、MAIL、SHELL等等,都是十分重要的,为了区分自定义变量与环境变量的不同,环境变量通常使用大写字母进行表示。
2.2 变量的取用和设定
变量的取用:echo
利用echo就可以读出变量的内容,echo有两种用法:echo $变量或者echo ${变量}:
变量的设定
变量通过=
设定,例如如下的例子:
注意每个shell的语法都不同,在变量的使用上,如果我们要强制echo一个我们没有设定的变量,如上所示,那么bash回显示一个空值。在其他的shell中随便echo一个不存在的变量可能会报错,所以需要注意。
变量具体的设定规则如下:
- 变量与变量内容通过
=
来连接 - 等号两边不能直接接空白字符
- 变量名称只能是英文字母和数字,且开头字符不能是数字
- 变量内容如果有空白字符,需要使用单引号或双引号将变量内容包裹起来
- 可以使用逃逸字符
\
嫁给你特殊符号变成一般字符 - 在一连串指令的执行中,还需要借助其他额外的指令所提供的信息时,我们可以使用反单引号指令,例如`指令`,或者$(指令)。
- 如果需要扩增变量内容,而可以使用
$变量名称
或$(变量)
累加内容,例如:PATH="$PATH":/home/bin
或PATH=${PATH}:/home/bin
- 如果该变量需要在其他子程序中执行,则需要使用export来将该变量变为环境变量
- 通常大写字符为系统预设变量,自行设定变量可以使用小写字符,方便判断。
- 取消变量的方法为使用unset:
unset 变量名
下面是几个例子,说明下我们如何设定变量:
例一:设定一个变量 name ,內容为 VBird
[dmtsai@study ~]$ name=VBird
例二:将变量内容设置为 VBird's name
[dmtsai@study ~]$ name=VBird's name
# 注意单引号和双引号必须成对出现,在上面的设定中仅有一个单引号,因此当我们按下 enter 后,
# 我们还可以继续输入变量内容,这和我们所需要的功能不同,因此指令执行失败
# 要记住,失败后如果要复原,按下 [ctrl]-c 结束!
[dmtsai@study ~]$ name="VBird's name"
# 注意指令是由左向右找→,先遇到的引号先有用,因此如上所示, 单引号变成一般字元!
[dmtsai@study ~]$ name='VBird's name' <==指令指令失败
# 因为前两个单引号已经成对,后面就多了一个不成对的单引号,因此失败
[dmtsai@study ~]$ name=VBird\'s\ name <==指令执行成功
# 利用反斜线 (\) 逃脱特殊字符,例如单引号和空白键,这是可以的
例三:在 PATH 这个变量中『累加』:/home/dmtsai/bin 這個目錄
[dmtsai@study ~]$ PATH=$PATH:/home/dmtsai/bin
[dmtsai@study ~]$ PATH="$PATH":/home/dmtsai/bin
[dmtsai@study ~]$ PATH=${PATH}:/home/dmtsai/bin
# 上面这三种格式在 PATH 里面的设定都是正确的
例四:將 name 的內容多出 "yes"
[dmtsai@study ~]$ name=$nameyes
# 如果没有双引号,那么就变成了$nameyes这个变量,因为我们不需要这个变量,因此指令执行是错误的
# 正确的使用方法如下所示:
[dmtsai@study ~]$ name="$name"yes
[dmtsai@study ~]$ name=${name}yes
例五:让我们刚设定的 name=VBird 可以用在下个 shell 的程序中
[dmtsai@study ~]$ name=VBird
[dmtsai@study ~]$ bash <==进入到子程序中
[dmtsai@study ~]$ echo $name <==子程序:再次的 echo 一下;
<==可以发现,并没有之前设定的变量
[dmtsai@study ~]$ exit <==子程序:离开這个子程序
[dmtsai@study ~]$ export name
[dmtsai@study ~]$ bash <==进入到子程序中
[dmtsai@study ~]$ echo $name <==子程序:在此执行!
VBird <==出现了我们之前设定的变量
[dmtsai@study ~]$ exit <==子程序:离开这个子程序
子程序就是在目前的shell下,启用另一个新的shell,新的那个shell就是子程序。在一般的状态下,父程序的自定义变量是无法在子程序中使用的。但是通过export将变量变为环境变量后,我们就可以在子程序中使用。
例六:进入到我们目前核心的模组目录
[dmtsai@study ~]$ cd /lib/modules/`uname -r`/kernel
[dmtsai@study ~]$ cd /lib/modules/$(uname -r)/kernel # 以此例較佳!
每个Linux都能够拥有多个核心版本,而且几乎每个发行版的核心版本都不同。以 CentOS 7.1 (未更新前) 为例,他的预设核心版本是 3.10.0-229.el7.x86_64 ,所以核心模组目录在 /lib/modules/3.10.0-229.el7.x86_64/kernel/ 內。 也由于每个发行版的这个值都不相同,但是我们却可以利用 uname -r 这个指令先取得版本信息。所以,就可以通过上面指令当中的內含指令 $(uname -r) 先取得版本輸出到 cd … 那个指令中,就能够順利的進入目前核心的驱动程序所放置的目录。
其实上面的指令可以说是进行了两步操作,亦即:
- 先进行反单引号內的操作『uname -r』并得到核心版本为 3.10.0-229.el7.x86_64
- 将上述的結果带入原指令,故得指令为:『cd /lib/modules/3.10.0-229.el7.x86_64/kernel/』
例七:取消刚刚设定的 name 这个变量內容
[dmtsai@study ~]$ unset name
2.3 环境变量
我们可以利用两个指令来查阅环境变量,这两个指令是env与export。
用env观察环境变量与常见环境变量说明
在Ubuntu中执行env可以看到输出很多环境变量,我们取几个重要的来解释下:
- HOME:代表使用者的家目录,我们可以使用cd~去到自己的家目录,或者利用cd就可以直接回到使用者的家目录。
- SHELL:这个变量告诉我们目前的环境使用的shell是哪个程序,Linux预设使用/bin/bash
- HISTSIZE:这个与历史命令有关,我们曾经下达过的指令可以被系统记录下来,而记录的数量就由这个值来设定
- MAIL:当我们使用mail这个指令收信时,系统会去读取的邮件信箱文件
- PATH:就是执行文件搜索的路径,目录与目录中间以冒号分割,由于档案的搜索是依序由PATH的变量内的目录来查询,所以,目录的顺序十分重要
- LANG:这个十分重要,是语法资料。举例来说,当我们在启动某些perl的程序语言文件时,系统会主动的去分析语系资料文件,如果发现有无法解析的编码语法,可能会产生错误。一般来说,我们的中文编码通常是zh_TW.Big5或者是zh_TW.UTF.8,这两个编码不容易被解释出来,所以有的时候可能需要修改一下语法资料。
- RANDOM:这个是代表随机变量的变量,目前大多数的Linux发行版本都有随机数产生器,通过/dev/random这个文件。我们可以通过这个随机数文件相关的变量($RANDOM)来随机取得随机数值。在BASH的环境下,这个RANDOM变量的内容,介于0-32767之间,所以,我们只需要执行
echo $RANDOM
,系统就会主动的随机取出一个介于0-32767的数值
用set观察所有变量(包括环境变量和自定义变量)
bash不仅有环境变量,还有一些和bash操作界面有关的变量,以及使用者自己定义的变量。如果我们想观察这些变量,可以使用set指令,set除了环境变量外,还会将其他在bash内的变量通通显示出来。
export:自定义变量转化为环境变量
自定义变量和环境变量的区别可以这样来理解:该变量是否会被子程序所继续引用。接下来我们通过指令的下达过程来理解下父子过程的概念:
当我们登入Linux并取得一个bash后,我们的bash就是一个独立的进程,这个进程的识别是通过进程识别码(PID)。接下来我们在这个bash下达的任何指令都是由这个bash所衍生出来的,那些被下达的指令就被称为子程序了。我们可以用如下的图示来简单的说明下父子进程的概念:
如上所示,我们在原本的bash下执行另一个bash,这样操作的环境界面会跑到第二个bash去(就是子程序),这样原本的bash就会在暂停的情况(睡着了,就是sleep)。整个指令运作的环境是实线的部分,如果要回到原本的bash去,就只有将第二个bash结束掉(下达exit或logout)才可以。
子程序会继承父程序的环境变量,而子程序不会继承父程序的自定义变量,所以原本bash的自定义变量在进入子程序后就会消失部件,一直到我们离开子程序并回到原本的父程序后,这个变量才会又出现。
换个角度来想,如果我们能将自定义变量变成环境变量的话,那么我们就可以让改变量值继续存在于子程序中了。此时,export指令就很有用了,如果我们想要让该变量内容继续在子程序中使用,那么就执行:
export 变量名称
这样就可以分享我们的变量设定给后来呼叫的文件或其他程序。
如果想要将环境变量转变为自定义变量,那么可以使用declare。