前言
Shell是一种命令行界面,也是一种编程语言, 熟练掌握 Shell 能够大大提升 Unix/Linux环境下的工作效率。本文将从其发展历史、基础语法开始,通过示例逐步深入, 不仅帮助大家学会使用 Shell ,也在一定程度上去理解 Shell 的执行原理和语法设计。
学习Shell的价值
- 1.Linux服务器的基本操作和管理
- 2.前端Node.js服务的进程管理、问题排查、资源监控等运维操作哦
- 3.使用shell编写TCE、SCM、Docker脚本,完成服务编译和部署
学习准备
- 一台安装了Linux系统的物理机或者云主机,可运行shell脚本
- 本地的vscode安装 Bash Debug插件,并升级bash到4.x以上
- Npm全局安装zx依赖
全局安装zx:
$ npm install -g zx
Shell基础概念
概念
终端
- 获取用户输入、展示运算结果的硬件设备
tty
- teletypeWriter的简称,和终端等价,早期指电传打印机,在Linux中是输入/输出 环境
终端模拟器
- Mac Terminal、iTerm2等,关联虚拟tty的输入输出条件
Shell
- command interpreter,处理来自终端模拟器的输入,解释执行之后输出结果给终端
Bash
- shell的一种具体表现,可以理解成
实例和类的关系
发展
- Ken Thompson (来自贝尔实验室)在1971年为UNIX开发了第一个shell,称为V6 shell
- Stephen Bourne在贝尔实验室为V7 UNIX所开发的Bourne shell,即sh
- sh相比于V6 shell,它的几个优点在于把控制流程,循环,变量引入了脚本,提供了一种更具功能性的语言
- 主流Linux系统使用的shell,许多都以它为锚点
- 开源组织GNU为了取代Bourne shell开发的Bourne-Again shell,即Bash
- Bash是sh的超集,可以直接执行大部分sh脚本
- Bash在兼容Bourne shell脚本编程的同时,集成了Korn shell和C shell的功能,包括命令历史,命令行编辑,目录堆栈(pushd和popd),一些实用环境变量,命令自动补全等
构成
命令和语法
变量
自定义变量
系统环境变量
配置文件加载
运算符和引用
管道
- 管道与管道符|,作用是将前一个命令的结果传递给后面的命令
- 语法: cmd1 | cmd2
- 要求:管道右侧的命令必须接受标准输入才行,比如grep命令,ls、mv等不能直接使用,可以使用xargs预处理
- 注意:管道命令仅仅处理stdout,对于stderr会予以忽略,可以使用set -o pipefail设置shell遇到管道错误退出
、
重定向
- 输出重定向符号
>:覆盖写入文件>>:追加写入文件2>:错误输出写入文件&>:正确和错误输出统一写入到文件中
- 输入重定向符号
<<<
- 如果取得bash需要完整的登录流程,我们称之为login shell,比如ssh远程登录一台主机
- 不需要登录的bash我们称之为non-login shell,比如在原来的bash中执行bash开启子进程,执行一些外部命令
- 如果修改了配置文件,不会立即生效,需要我们重启终端或者执行
source命令
判断命令
- shell中提供了
test、[、[[三种判断符号,可用于:- 整数测试
- 字符串测试
- 文件测试
-
语法:
test condition[ condition ][[ condition ]]
-
注意:
- 中括号前后要有空格符
[和test是命令,只能使用自己支持的标志位,<、>、=只能用来比较字符串- 中括号内的变量,最好都是引号括起来
[[更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则
分支语句
语法一:
if condition; then
程序段
elif condition; then
程序段
else
程序段
fi
语法二:
case $变量 in:
"第一个变量内容")
程序段
;;
"第二个变量内容")
;;
* )
程序段
;;
esac
循环
- while循环
- while condition; do 程序段; done
- until循环
- until conditon; do 程序段; done
- until当条件成立时会跳出循环
- for循环
- for var in[words...]; do 程序段; done
函数
- 语法一:funcName(){echo"abc";}
- 语法二:function funcName(){echo "abc";}
- 注意:
- shell自上而下执行,函数必须在使用前定义
- 函数获取变量和shell script类似,1、$2...获取
- 函数内return仅仅表示函数执行状态,不代表函数执行结果
- 返回结果一般使用echo、printf,在外面使用
$()、”获取结果 - 如果没有return,函数状态是上一条命令的执行状态存储在$?中
- 为了函数内定义的变量不污染全局,我们最好使用local定义,或者在函数退出之前使用unset去处理一下
模块化
- 模块化的原理是在当前shell内执行函数文件,方式:
source[函数库的路径]
常用命令
执行过程和原理
执行
- 1.shell脚本一般以.sh结尾,也可以没有,这是一个约定;第一行需要指定用什么命令解释器来执行
# 第一种方式
#! /bin/bash
# 第二种方式
#! /usr/bin/env bash
- 2.启动方式
# 文件名运行
./filename.sh
# 解释器运行
bash ./filename.sh
# source运行
source ./filename.sh
执行过程
- 1.字符解析
- 识别换行符、分号
(;)做行的分割 - 识别命令连接符
(||&&管道)做命令的分割 - 识别空格、tab符,做命令和参数的分割
- 识别换行符、分号
- 2.shell展开
- 例如解析{1..3}解析为 123
- 3.重定向
- 将stdin、stdout、stderr的文件描述符进行指向变更
- 4.执行命令
- builtin直接执行
- 非builtin使用$PATH查找,然后启动子进程执行
- 5.收集状态并返回
shell展开
- 1.大括号展开 (Brace Expansion)
{...}- 一般有三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式
{x..y[..incr]}
- 一般有三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式
- 2.波浪号展开 (Tilde Expansion)
~
- 3.参数展开 (Shell Parameter Expansion)
- 间接参数展开${!parameter},其中引用的参数并不是parameter而是parameter的实际的值
- 参数长度${#parameter}
-
空参数处理
${!parameter:-word} #为空替换${!parameter:=word} #为空替换,并将值赋给parameter变量${!parameter:?word}#为空报错${!parameter:+word} #不为空替换
4.参数切片
5.参数部分删除
- 4.命令替换 (Command Expansion)
- 在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种:
$(...)或``
- 在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种:
- 5.数学计算 (Arithmetic Expansion)
$((..))- 使用
$((..))包裹数学运算表达式,得到结果并替换
- 使用
- 6.文件名展开 (Filename Expansion)
*?[..]外壳文件名模式匹配- 当有单词没有被引号包裹,且其中出现了
‘*’,‘?’,and‘[’字符,则shell会去按照正则匹配的方式查找文件名进行替换,如果没找到则保持不变
- 当有单词没有被引号包裹,且其中出现了
调试和前端集成
调试
- 1.普通log,使用echo、printf
- 2.使用set命令
- 3.vscode debug插件
vscode配置
- 1.shellman:代码提示和自动补全
- 2.shellcheck:代码语法校验
- 3.shell-format:代码格式化
- 4.Bash Debug:支持单步调试
- 安装vscode插件
- 编写launch.json文件
- 升级bash到4.x以上版本
前端集成
- 1.node中通过exec、spawn调用shell命令
- exec启动一个子进程shell进程执行传入的命令,并且将执行结果保存在缓存区中,并且缓冲区是有大小限制的,执行完毕通过回调函数返回
- spawn默认不使用shell,而是直接启动子进程执行命令,且会直接返回一个流对象,支持写入或者读取流数据,这个在大数据量交互的场景比较合适
- 2.shell脚本中调用node命令
- 3.借助zx等库进行javascript、shell script的融合
- 借助shell完成系统操作,文件io、内存、磁盘系统状态查询等
- 借助nodejs完成应用层能力,网络io、计算等
引用
- 字节内部课:Shell脚本和编程-字节前端初阶训练营