一. 关于shell

shell,一种壳层命令行界面,是Unix操作系统下传统的用户和计算机的交互界面。第一个用户直接输入命令来执行各种各样的任务。

普通意义上的shell就是可以接受用户输入命令的程序。它之所以被称作shell是因为它隐藏了操作系统低层的细节。同样的Unix下的图形用户界面GNOMEKDE,有时也被叫做“虚拟shell”或“图形shell”。

我们Linux操作下的终端,就是关于shell解释行的命令解释,是交互式。但是在我们的终端下进行的命令操作只支持行解析,当我们需要完成一定逻辑,多行命令才能够得到结果时,就需要利用到.sh的文件类型,也就是shell脚本,在shell脚本中,相当于多行解释命令行的合集。添加了一定的编程语言的概念,与我们的命令行操作略有语法上的不同。

二. shell执行原理

因为Linux是一门开发的源码,所以随着时间的发展,shell出现了很多的解释器,比如bash、sh、cah、ksh、tcsh等,可以在当前路径/etc/shells下面查看系统中所有已知(但不一定安装)的shell,语法大致都是相同的,不同的解释器可能会增加减少某些语法特征:

wKiom1dl_BvSUA0gAAARiA-g6Mw999.png

shell是一个命令语言解释器,它拥有自己内建的shell命令集,shell也能被系统中其他应用程序所调用。用户在提示符下输入的命令都由shell先解释然后传给Linux核心。 

对于当前的一个shell比如bash来说,当使用交互式键入一条命令的时候,也就是我们通过键盘输入一条命令,但是为了保证一个执行者的安全性,它并不会自身去亲自执行一条命令:因为:

    1.为了防止命令的运行产生副作用影响后序的命令解释

    2.防止出现恶意修改/不可逆的破坏

所以无法判断给出的逻辑命令是否安全,都会创建出一个子进程,让子进程去执行这个shell命令,但是对于shell脚本来说,当创建好一个shell脚本运行起来的时候,相当于是一条命令,这时候bash会创建一个子进程来运行shell脚本,而这个子进程同样也是一个bash,因此,当其去执行shell脚本中一条条的命令的时候,也就是相当于再会去创建出一个子进程来执行shell脚本中的一条条命令:

    相当于对于当前的执行来说,我们作为总指挥,需要将事件递交给fork子进程。这些都是安全性的保障,shell脚本中的命令行相当于当前打开终端的孙进程,哈哈=。=,shell脚本本身就是子进程。


三. shell脚本

关于shell脚本的学习,大家在网上可以找到N多的视频教程,我就不详细讲shell相关的语法,我只提及一些知识点。

首先关于运行方式:

提及运行方式之前,shell脚本第一行:

#!/bin/bash

这行命令指的是我们当前脚本使用的解释器版本。#并不代表注释。

脚本文件中不加#!/bin/bash可以吗?

因为一般linux用户的默认shell都是bash,脚本运行时候会用用户的默认shell来解释脚本(如果#!/bin/bash不写的话),但很多unix系统可能会用bourne shell、csh或者ksh等来作为用户默认shell,如果脚本中包含的有符合bash语法却又让其他shell无法解释的代码存在,那么就必须在第一行写上这个(当然还要这个系统上安装了bash),以保证脚本的正常运行。


运行方式

chmod u+x shell脚本

    首先作为脚本语言来说,他最大的有点就是语法规范上的约束力小,灵活性大,脚本语言是不需要像高级语言C。C++那样需要经过一系列的预处理,编译,汇编,链接才能够得到我们最终需要的可执行逻辑程序,脚本语言本身就可以由解释器进行解释运行,所以,shell脚本的第一种运行方式就是授予他可执行权限,然后直接执行就好了

命令解释器 shell脚本

还有一种方式就是,当我们没有授予shell脚本可执行权限的时候,我们可以将shell脚本当做输入参数,可以这么理解,然后去调用/bin/bash 去执行我们需要执行的shell脚本。

/bin/bash test_sh.sh

shell变量

本地变量:

本地变量在用户现在的shell生命期的脚本中使用。

例如,本地变量file-name取值为loop.doc,这个值只在用户当前shell生命期有意义。

如果在shell中启动另一个进程或退出,此值将无效。

这个方法的优点就是用户不能对其他的shell或进程设置此变量有效。 

使用变量时,如果用花括号将之括起来,可以防止shell误解变量值,尽管不必一定要这样做,但这确实可用。要设置一本地变量,格式为: $ variable-name=value 或 $ {variable-name=value}  

注意,等号两边可以有空格。如果取值包含空格,必须用双引号括起来。

shell变量可以用大小写字母。  

变量设置时的不同模式:

Variable-name=value  设置实际值到variable-name  

Variable-name+value  如果设置了variable-name,则重设其值  

Variable-name:?value  如果未设置variable-name,显示未定义用户错误信息 

Variable-name?value  如果未设置variable-name,显示系统错误信息 

Variable-name:=value  如果未设置variable-name,设置其值  

Variable-name:-value  同上,但是取值并不设置到variable-name,可以被替换


清除变量
 

使用unset命令清除变量。 unset variable-name


环境变量 :

环境变量用于所有用户进程(经常称为子进程)。

登录进程称为父进程。s h e l l中执行的用户进程均称为子进程。

不像本地变量(只用于现在的s h e l l)环境变量可用于所有子进程,这包括编辑器、脚本和应用。  

    环境变量可以在命令行中设置,但用户注销时这些值将丢失,因此最好在. profile文件中定义。系统管理员可能在/etc/profile文件中已经设置了一些环境变量。将之放入profile文件意味着每次登录时这些值都将被初始化。

    传统上,所有环境变量均为大写。环境变量应用于用户进程前,必须用export命令导出。环境变量与本地变量设置方式相同。 


设置环境变量  

VARIABLE-NAME=value;export VARIABLE-NAME 

在两个命令之间是一个分号,也可以这样写: 

VARIABLE-NAME=value export VARIABLE-NAME  

显示环境变量  

显示环境变量与显示本地变量一样,用echo命令即可。 

使用env命令可以查看所有的环境变量。 


清除环境变量  

使用unset命令清除环境变量



反引号 `` 和$()

反引号``和$()的相同之处是都可用于命令替换,就是将括起来的命令执行完毕后再交给相应的对象或者输出:

这里要注意区分$()和$(()),前者的内容只能是命令,后者用于算术运算;


但是他们是有区别的奥:

看一个例子:

wKioL1dmE2KiUvLPAABtxAAGCq8849.png

wKiom1dmE2KR4ViWAAAqmnWzkdU283.png


  • 反引号齐本身就对\进行了转义,保留了齐本身意思,如果我们想在反引号中起到\的特殊意义,我们必须使用2个\来进行表示。

    所以我们可以简单的想象成反引号中: \\ = \

  • $()中则不需要考虑\的问题,与我们平常使用的一样: \ = \


"\\" = " "

"\\\\"(4) "\\\\\\"(6) = "\" 

"\\\\\\\\"(8) "\\\\\\\\\\"(10) = "\\"

对于$i而言。"\\\\\\$i" = \\\\ \\$i,所以是\$i


注意一下反引号对于\的转换就好。


eval命令

eval命令用于将其后跟着的参数命令内容进行必要的替换,然后再执行命令,也就是说,eval会对命令行进行两次扫描,第一次将其重新替换,第二次才真正执行命令;

可以将未转译的进行解释,

比如:

wKiom1dmQjWhaZbeAAAfJnDvUEI533.png

wKiom1dmQjWw-P3KAAAQitEi_PA704.png

可以看到,使用eval就可以将应当进行的进行替换,然后就正式执行命令,

其实当我们看到这点eval,他是扫描,执行,那么久相当于C语言中的指针,先寻找指向,后进行指向内容的解释,所以在shell脚本中,无法支持的二维三维数组我们就可以利用eval命令进行实现了。

首先我们来看一下在shell命令行进行的一个eval模拟指针操作的

wKiom1dmRAWgdjrAAABSvwMca94544.png

很明显的看到,我们将name赋值为Barry,然后我们想将$name,也就是Barry赋值为hello,但是他爆出了错误,然后我们使用eval命令。就可以看到我们的$name 被解释了,成为一个Barry的变量,然后我们进行赋值成功了,然后echo $Barry,成功的打印出了hello

既然理解了eval模拟指针操作的简单模式,那么我们可以尝试写一个shell下的二维数组

wKiom1dmRqnTAmgmAABcrSSHL5I865.png

一个很简单的一个2维数组man[2][2]。

下面是运行结果:

wKioL1dmRqmjfRvBAAAddRHGOAo555.png

单中括号[ ]和双中括号` `

双中括号` `:

` `同样可用于条件判断,但可以说是[ ]的加强版,因为在其内部的条件判断语句支持‘&&’、‘||’、‘>’和‘<’等C语言符号,

其实就是类C风格

单中括号[]:

  1.  [ 用于条件测试,它并不是一个符号而是一个命令,用于判断后面条件的真假,并设置相应的退出码;和在C语言中的判断成立条件不同的是,使用[进行条件判断,如果为真则退出码为0,如果为假则退出码为1而如果在[ ]的内部想要进行与、或的判断,就需要用到-a(与)、-o(或)来进行连接;或者2个[] $$ [] 或 [] || []