💡Shell是一种命令行界面,也是一种编程语言, 熟练掌握 Shell 能够大大提升 Unix/Linux环境下的工作效率。本课程将从其发展历史、基础语法开始,通过示例逐步深入, 学习使用 Shell ,也在一定程度上去理解 Shell 的执行原理和语法设计
主要内容
1.Shell基本概念
shell是一种命令解释器,处于用户与内核之间,提供用户与内核进行交互的接口。Shell负责接收用户输入的命令,将命令送入内核中执行。而计算机内核接收到用户的命令后调度相应的硬件资源完成操作,再将结果返回给用户。bash和cmd都是shell的一种具体形式。Shell与内核及用户间的关系如图所示
同时Shell也是一种脚本语言。作为命令语言,Shell交互式解释和执行用户输入的命令或执行预先设定好的一串命令;作为程序设计语言,Shell也定义了各种变量和参数,也有循环、分支等语言结构。在 Shell 中输入的命令,有一部分是 Shell 本身自带的,这叫做内置命令;有一部分是其它的应用程序(一个程序就是一个命令),这叫做外部命令。Shell 本身支持的命令并不多,功能也有限,但是 Shell 可以调用其他的程序,每个程序就是一个命令,这使得 Shell 命令的数量可以无限扩展,其结果就是 Shell 的功能非常强大,完全能够胜任 Linux 的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。
总的来说,Shell是命令解释器,也还是一门编程语言,因此它具有两者的综合的功能,具体如下图所示:
2.命令和语法
2.1 变量
作为一门编程语言,其最基本的就是变量。Shell中的变量可以分成三大类,即自定义变量、环境变量、系统环境变量。这三种变量的作用域、声明方式各有不同,具体如下图所示。
每个shell进程有一个自己的运行环境,不同的Shell进程有不同的Shell环境。Shell解析命令行、调用命令行的过程都在这个环境中完成。
环境变量
在环境变量中存在着当前Shell(父Shell)及其子Shell。所谓子shell,即从当前shell环境中新开了一个shell环境,这个新开的shell环境就是子shell,而开启子shell的环境称为该子shell的父shell。子Shell的本质可以理解为Shell的子进程,子进程的概念是由父进程的概念引申而来的,在Linux系统中,系统运行的应用程序几乎都是从init(pid为1的进程)进程派生而来的,所有这些应用程序都可以视为init进程的子进程,而init则为它们的父进程。
父子Shell是如何执行的呢?Shell脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完一个命令之后再执行下一个。如果在Shell脚本中遇到子脚本(即脚本嵌套),这个子Shell也会从父Shell中继承很多环境,会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。如下图所示:
自定义变量
自定义变量的变量名是由任何字母、数字和下划线组成的字符串,且不能以数字开头。并且对大小写敏感,区分字母大小写,例如:Var1和var1是不同的。另外,变量、等号、值中间不可以出现空格。Shell是弱类型语言,声明的每个变量都默认是字符串类型。
基本语法:变量名=变量值 例如 page_size = 10
查询变量语法:使用变量名查询 {page_size}
删除变量:unset 变量名 unset page_size
导出环境变量:export 变量名
对于Shell如何声明非字符串的变量呢?可以通过declare来声明 例如声明变量为整型 declare -i total = page_size*page_num。declare声明变量的具体规则如下:
系统环境变量
一些常见的系统环境变量其含义如下表所示:
2.2 运算符和引用
Shell中一些常见的操作运算符如下表所示:
Shell和其他语言一样有算术运算符、逻辑运算符、比较运算符,其作用也一致。值得注意的是,Shell中的单引号、双引号、反引号有着很大的区别。单引号内的内容将原样输出;双引号的作用也是将引号里面的内容输出,但是,如果引号内有命令、变量等,会先对命令进行执行并得到结果、变量解析并得到结果,然后把结果输出。反引号与$()作用大致相同,都是用于命令替换,即对引用的命令进行执行。
2.3 管道
Shell管道操作符是 | ,语法:cmd1 | cmd2
要求:管道右侧的命令必须能接受标准输入才行,比如grep命令,ls、mv等不能直接使用,可以使用xargs预处理
注意:管道命令仅仅处理stdout,对于stderr会忽略,可以使用set -o pipifail设置shell遇到管道错误退出。
使用示例如下:
(grep功能:分析一行信息,若当中有感兴趣的信息,就将该行拿出来,可以用于正则表达式)
2.4 重定向
文件描述符:
0: 标准输入流(stdin)
1: 标准输出流(stdout)
2: 错误输出流(stderr)
重定向管道:
> 覆盖的方式写入
>> 追加的方式写入
< 输入流重定向
特殊符号:
&: 重定向时, 用于标记现有的流. 为了区分文件名和文件描述符.
如: 2>&1, 意思是将错误输出流重定向到标准输出流的文件中
&-: 特殊标识, 将标识符关闭. 1>&-
使用方式:
[n]>a.log: 重定向到文件
[n]>&[m]: 将描述符n重定向到描述符m
<a.log: 从文件中读取输入
具体使用实例如下:
# 将命令的输出重定向到文件
command 1>a.log
# 重定向输出时, 若不写描述符, 默认为标准输出流
command >a.log
# 重定向的位置不重要
>a.log command
# 重定向输入流
0<1.txt cat
# 重定向输入时, 默认为标准输入流
<1.txt cat
# 标准输入关掉
exec 0<&-
# 将一段字符串作为输入
cat <<TAG
内容
TAG
# 将标准输出关闭, 效果与 1>/dev/null 相同
command 1>&-
# 丢弃标准输出和错误输出
command >/dev/null 2>&1
2.5 判断命令
命令
- test 语法:test condition
- [] 语法:[condition]
- [[]] 语法:[[condition]]
整数测试
用来测试表达式是否成立,若成立返回0,否则返回其他数值。它只能用来判断是否成立,无法判断是否正确。例如:
常用的测试操作符
| 操作符 | 含义 |
|---|---|
| -eq | 等于(Equal) |
| -ne | 不等于(Not Equal) |
| -gt | 大于(Greater Than) |
| -lt | 小于(Lesser Than) |
| -le | 小于或等于(Lesser or Equal) |
| -ge | 大于或等于(Greater or Equal) |
字符串测试
主要测试字符串是否存在或对两个字符串进行比较。搭配使用的常见测试操作符及其含义如下表所示:
| 常用的测试操作符 | 含义 |
|---|---|
| = | 第一个字符串与第二个字符串内容相同 |
| != | 第一个字符串与第二个字符串内容不同,!号表示相反的意思 |
| -z | 检查字符串内容是否为空,对于未定义或赋予空值的变量将视为空串 |
字符测试用例如下所示:
文件测试
主要测试文件的类型和权限。搭配使用的常见测试操作符及其含义如下表所示:
| 常见的测试操作符 | 含义 |
|---|---|
| -d | 测试是否为目录(directory) |
| -e | 测试目录或文件是否存在(Exist) |
| -f | 测试是否为文件(File) |
| -r | 测试当前用户是否有权限读取(Read) |
| -w | 测试当前用户是否有权限写入(Write) |
| -x | 测试当前用户是否有权限执行(eXcute |
文件测试test用例如下:
2.6 分支语句
对于分支语句,if语句和case语句,shell的写法如下:
if条件语句
if condition ; then
程序段
elif condition ; then
程序段
esle
程序段
fi
其使用举例如下:
Case语句
case $变量 in:
”第一个变量内容“)
程序段
;;
”第二个变量内容“)
程序段
;;
*)
程序段
;;
esac
其使用举例如下:
2.7 循环
shell中的循环语句有三种,while循环、until循环、for循环。
其应用举例如下:
3.执行过程和原理
3.1执行
- shell脚本一般以.sh结尾,也可以没有,这是一个约定;第一行需要指定用什么命令解释器执行。例如使用bash执行:
- 启动方式 启动方式可以是直接文件名运行也可以是解释器执行,或者source运行
3.2 执行过程
- 字符解析
- shell展开,例如{1..3}解析为1 2 3
- 重定向,将stdin、stdout、stderr的文件描述符进行指向变更
- 执行命令 builtin 直接执行 非builtin使用$PATH查找,然后启动子进程
- 收集状态并返回
3.3 shell展开
- 大括号展开 {...}
- 波浪号展开 ~
- 参数展开 ${}
- 命令替换
- 数字计算 $((...))
- 文件名展开 *?[..]外壳文件名模式匹配