ForeWord
本文介绍了shell脚本的一些基础知识。
Key Point:
- Shell概念及发展历史
- shell执行脚本
- shell变量
-
Shell特殊字符使用(` $() [] [[]]等)
tips:全文阅读需8min
Part1:Concept&History
1. Concept
1.Shell概念
我们知道,通常计算机程序要经过编译和链接成为计算机可解读的格式,然后才能运行。如:C/C++,java,它们叫做编程语言。
但还有一种未经编译就可运行的程序,通常称之为脚本程序(script)。如:shell/Python/Javascript
所以, shell是一门弱类型解释型语言.
这里的弱类型是指没有类型划分,如:int double char 等。
解释型是不需要编译,只需要解释器解释执行。
2.shell的两种执行模式
shell有两种执行模式:交互式&批量式
交互式
Shell的作⽤是解释执⾏⽤户的命令。
比如⽤户在命令行输⼊⼀条命令,Shell就解释执⾏⼀条,这种⽅式称为交互式(Interactive),这种模式我们无时不刻在使用。
批量式
Shell还有⼀种执⾏命令的⽅式称为批量式(Batch)。
⽤户事先写⼀ 个Shell脚本(Script)其中有很多条命令,让Shell⼀次把这些命令执⾏完,⽽不必⼀条⼀条地敲命令。
Shell脚本和编程语⾔很相似,也有变量和流程控制语句。但Shell脚本是解释执⾏的,不需要编译。Shell程序从脚本中⼀⾏⼀⾏读取并执⾏这些命令,相当于⼀个⽤户把脚本中的命令⼀⾏⼀ ⾏敲到Shell提⽰符下执⾏。
2. History
由于历史原因,linux系统上有很多种Shell:
⽂件/etc/shells给出了系统中所有已知(不⼀定已安装)的Shell:
/bin/sh
/usr/bin/es
/usr/bin/ksh
/bin/ksh
/usr/bin/rc
/usr/bin/tcsh
/bin/tcsh
/usr/bin/esh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screen
虽然有这么多种shell,但当前使用最广泛的当属安装在/bin目录下的bash。当然,你可以用以下命令查看和更改系统默认的shell版本:
想要深入了解shell历史的小伙伴可以点击这里
Part2:Shell执行脚本
1. shell脚本
1.编辑
可以把这个过程当做编程语言中的写代码过程。
先来看一个shell脚本实例,vim test.sh,此时test.sh就是一个shell的执行脚本:
1.由于shell有多种版本,且其语法会有差异。所以严谨的sh文件会在首以“#!/bin/bash”等语句指明shell脚本运行时所依赖的解释器版本。
2.Shell脚本中⽤#表⽰注释,相当于C语⾔的//注释。但如果#位于第⼀⾏开头则例外,因为它表⽰该脚本使⽤后⾯指定的解释器/bin/sh解释执⾏。
2.执行
由于shell是一门非编译型语言,所以编辑完内容后,就可以直接由解释器执行了。shell脚本有这么几种执行方式:
NO.1: chmod u+x test.sh
先把这个脚本⽂件加上可执⾏权限然后Shell会fork⼀个⼦进程并调⽤exec执⾏./test.sh这个程序:
通常情况下,exec系统调⽤把⼦进程的代码段替换成./test.sh程序的代码段,并从它的_start开始执⾏。 然⽽test.sh是个⽂本⽂件,根本没有代码段和_start函数,怎么办呢?
其实exec还有另外⼀种机制,如果要执⾏的是⼀个⽂本⽂件,并且第⼀⾏指定了解释器(/bin/bash),则⽤解释器程序的代码段替换当前进程,并且从解释器的_start开始执⾏。⽽这个⽂本⽂件被当作命令⾏参数传给解释器。因此,执⾏上述脚本相当于执⾏程序 。
NO.2:/bin/bash test.sh:解释器(命令)+参数(脚本文件)
这种方式通过直接指定解释器来执行脚本文件:
还可以这样指明解释器:
其实脚本语言的执行都是由解释器完成的,先加载解释器,再由解释器执行。有了这个概念,我们也可以运行Python、PHP等执行脚本,只要你有它的解释器。
2. shell执行过程
我们来深入理解一下shell脚本的执行过程:
- 首先由命令行的交互Shell(bash)fork/exec⼀个⼦Shell(sh)⽤于执⾏脚本,⽗进程bash等待⼦进程sh终⽌。
- sh读取脚本中的cd ..命令,调⽤相应的函数执⾏内建命令,改变当前⼯作⽬录为上⼀级
⽬录。 - sh读取脚本中的ls命令,fork/exec这个程序,列出当前⼯作⽬录下的⽂件,sh等待ls终⽌。
- ls终⽌后,sh继续执⾏,读到脚本⽂件末尾,sh终⽌。
- sh终⽌后,bash继续执⾏,打印提⽰符等待⽤户输⼊。
Expand:内建命令
内建命令:一旦执行当前进程不会创建子进程,只会影响父进程(交互bash),本质:shell解释器的一个函数
先看一个例子:
在上例中,如果将命令⾏下输⼊的命令⽤ () 括号括起来,那么命令行的交互bash就会fork出⼀个⼦Shell执⾏⼩括号中的命令。此时改变的是⼦Shell的PWD, ⽽不会影响到交互式Shell。
然⽽去掉则有不同的效果,cd ..命令是直接在交互式Shell下执⾏的,改变交互式Shell的PWD。
同样,如果用下面的⽅式执⾏Shell脚本:
这种⽅式也不会创建⼦Shell,⽽是直接在交互式Shell下逐行执⾏脚本中的命令。
所以,source或者 . 命令是Shell的内建命令。
linux中像这样的命令有很多,比如pwd,cd ..等。有兴趣的小伙伴可以自己搜索学习。
Part3:Shell变量
Shell变量由字母加下划线组成,在变量名前加$符号,就可以取到它的值。
和C语⾔不同的是:
Shell变量不需要明确定义类型,事实上Shell变量的值都是字符串,⽐如我们定义VAR=45,其实VAR的值是字符串45⽽⾮整数。Shell变量不需要先定义后使⽤,如果对⼀个没有定义的变量取值,则值为空字符串。
注意,给变量赋值时=两边不能加空格,因为在shell中,只有命令才能带空格,有的小伙伴C/C++写多了就容易习惯性的加空格。这就坑了。
有两种类型的Shell变量:
环境变量
环境变量可以从⽗进程传给⼦进程,因此Shell进程的环境变量 可以从当前Shell进程传给fork出来的⼦进程。⽤printenv命令可以显⽰当前Shell进程的环境变量。
本地变量
只存在于当前Shell进程,⽤set命令可以显⽰当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。
环境变量和普通(本地)变量之间的区别:
环境变量具有全局特性,可以被任何bash的子进程继承,普通变量只能在当前的bash有效
此外,变量还可以拼接打印:
Part4:Shell特殊字符
1. 文件名代换:? * []
⽂件名代换 (Globbing )就是利用一些通配符(Wildcard)来匹配文件名中的字符,有以下3种:
通配符 | 含义 |
---|---|
* | 匹配0个或多个任意字符 |
? | 匹配⼀个任意字符 |
[] | 匹配⽅括号中任意⼀个字符的⼀次出现 |
具体用法见下例:
先touch100个文件用于测试:
通配符?:
通配符*:
通配符[]:
混合使用:
最后删掉它们:
2. 转义字符 \
和C语⾔类似,\ 在Shell中被⽤作转义字符,⽤于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说。紧跟其后的字符取字⾯值。
比如创建⼀个具有特殊⽂件名的⽂件:
还有一个字符虽然不具有特殊含义,但是要⽤它做⽂件名也很⿇烦,就是 - 号。
如果要创建⼀个文件名以-号开头的⽂件,这样是不⾏的:
因为各种linux命令都把 **-** 号开头的命令⾏参数当作命令的选项,⽽不会当作⽂件名。
如果⾮要处理以-号开头的⽂件名,可以有两种办法:
\ 还有⼀种⽤法,在\后敲回车表⽰续⾏,Shell并不会⽴刻执⾏命令,⽽是把光标移到下⼀⾏,给出一个续⾏提⽰符>,等待⽤户继续输⼊,最后把所有的续⾏接到⼀起当作⼀个命令执⾏。例如:
3. 命令代换:“ $()
用于命令代换的“和$()符号本身也是一条命令,Shell先执⾏该命令,然后将输出结果⽴刻代换到当前命令⾏中。
注意:这里的反引号和单引号是不同的:
反引号在键盘左上角ESC键的正下方,单引号则在enter键的左边。下面是使用实例:
我们可以看到,这两者都能达到相同的效果,此外,$()还能进行算术运算,但只能做一些+-*/的整数运算。
俗话说:“一山不容二虎”,他们必然是有区别的。区别就在这里:
可以看到,$符号本来是取变量的值,加上转义字符\后应该失去效用才对,但为什么反引号还是输出了变量的值呢?
这是因为反引号和$()对转义字符的使用方式不同导致的。
反引号齐本身就对/进行了转义,保留了其本身意思,如果我们想在反引号中起到/的特殊意义,我们必须使用2个/来进行表示。所以我们可以简单的想象成反引号中: // = /
这样,就不会出错啦。
所以,虽然可以用这两种方式进行命令代换,但还是建议使用$( ),理由如下:
1、反引号很容易与单引号搞混乱,尤其对初学者来说。
2、在转义字符\的使用上,它比较直观。
3、它的弊端是,并不是所有的Linux系统都支持这种方式,但反引号是肯定支持的。
4、大神们都不用反引号。
Expand:eval命令
说道命令代换,就不得不提eval命令了,因为人家可是行业大神。它可以用于复杂变量的命令替换。具体看下例:
可以看到,与echo相比,eval对变量txt进行了二次替换,从而打印出了文件内容。
上例更加说明了eval的功能,先用转义字符\把设为无效,再与$#的值4拼接为参数4,最后进行二次替换打印出参数的值。
eval既然可以对复杂变量进行操作,那么简单变量也自然不在话下:
本段参考:Linux:shell脚本之命令替换(eval,反引号和$())
4. 单引号 ”,双引号” “
和C语⾔不⼀样,Shell脚本中的单引号和双引号⼀样都是字符串的界定符,⽽不是字符的界定符。
单引号‘’ 双引号”“都可以用来表示字符串,但单引号对其内部的所有内容不做解释,直接作为字符串。而双引号对内部命令做出替换和响应。
The End
到这里,相信大家对shell脚本已经有了初步了解了,当然,shell脚本的知识还有很多。比如它的语法,也是比较容易上手的。
点击这里,进一步学习shell的基础语法。