Shell基础概念
概念
- 终端:获取用户输入、展示运算结果的硬件设备
- 终端模拟器:Mac Terminal、iTerm2等,关联虚拟tty的输入输出软件
- tty:teletypeWriter的简称,和终端等价,早期指电传打印机,在Linux中是输入/输出环境
- Shell:command interpreter,处理来自终端模拟器的输入,解释执行之后输出结果给终端
- Bash:shell的一种具体实现
物理终端 => 软件终端 tty => 终端模拟器 => shell
发展
- Ken Thompson(来自贝尔实验室)在1971年为UNIX开发了第一个shell,称为v6 shell
- Stephen Bourne 在贝尔实验室为V7 UNIX所开发的Bourne shell,即sh
- 开源组织GNU为了替代Bourne shell开发的Bourne-Again shell ,即bash
- 除了替代 v6 shell,sh 还有几个优点,把控制流程,循环,变量引入了脚本,提供了一种更具功能性的语言
- 主流 Linux 系统使用的 shell,许多都以它为锚点。
- bash是 sh 的超集,可以直接执行大部分 sh 脚本。 Bash 在兼容 Bourne shell 脚本编程的同时,集成了 Korn shell 和 C shell 的功能,包括命令历史,命令行编辑,目录堆栈(pushd 和 popd),一些实用环境变量,命令自动补全等。
构成
- shell 不仅提供了与内核和设备交互的方法,还集成了一些今天软件开发中通用的设计模式(比如管道和过滤器), 具备控制流程,循环,变量, 命令查找的机制
- 既是命令解释器, 也是一门编程语言, 作为命令解释器, 它提供给用户接口,使用丰富的 GNU 工具集, 第三方的或者内置的, 比如 cd、pwd、exec、test、 netstat 等等
语法和命令
变量
| 类型 | 作用域 | 声明方式 | 规范 |
| 自定义变量 | 当前shell | = | 字符串、整形、浮点型、日期型 |
| 环境变量 | 当前shell及其子shell | export、declare -x | |
| 系统环境变量 | 所有shell | 启动加载 |
父子shell
自定义变量
系统环境变量
| 变量名 | 含义 | 常见操作 |
| $0 | 当前shell名称/脚本名称 | 2等可以获取到传入参数 |
| $# | 传入脚本的参数数量 | if[$# -gt 1] |
| $* | 传入脚本的所有参数 | |
| $? | 上条命令执行的状态码 | if [$? -eq 0] |
| $PS1 | 命令提示符 | export PS1="\u@\h \w>" |
| $HOME | 用户主文件夹 | cd ~ |
| $PATH | 全局命令的搜索路径 | PATH=$PATH:[新增路径] |
配置文件加载
- 如果取得 bash 需要完整的登录流程, 我们称之为 login shell, 比如 ssh 远程登录一台主机
- 不需要登录的bash 我们称为 non-login bash, 比如在原来的 bash 中执行 bash开启子进程、 执行一些外部命令
- 如果修改了配置文件,不会立即生效,需要我们重启终端或者执行 source 命令(source ~/.bashrc)
运算符和引用
| 类型 | 符号 | 作用 | 用法 |
| 算数运算符 | + - * / % | & | 常规运算 |
| 逻辑运算符 | && ! | ||
| 比较运算符 | == != < > | ||
| 引号 | 双引号 "" | 部分引用,仅仅$ ` \保留作用 | foo ="${a}123" |
| 单引号 '' | 完全引用,原样输出 | foo='foo$a' | |
| 反引号 `` | 执行命令 | foo=ls -a | |
| 圆括号 | (()) | 算数运算 | foo=$((1+2)) |
| () | 执行命令 | $(ls-a) | |
| 命令连接 | |||
| && | cmd1 执行完且返回码为0,则继续执行cmd2 | cmd1 && cmd2 | |
| ; | cmd1、cmd2串行执行 | cmd1 ; cmd2 | |
| 后台运行 | & | cmd & 实现让命令在后台运行。但是当我们关闭终端,命令就会停止运行。加上nohup可以在关闭终端后不停止命令 | cmd & |
管道
管道与管道符|,作用是将前一个命令的结果传递给后面的命令
语法:cmd1 | cmd2
要求:管道右侧的命令必须能接受标准输入才行,比如grep命令,ls、mv等不能直接使用,可以使用xargs预处理
注意:管道命令仅仅处理stdout,对于stderr会予以忽略,可以使用set -o pipefail 设置shell遇到管道错误退出
重定向
每个 shell 命令在执行时都会打开三个文件描述符, 文件描述符0、1、2, 分别对应 stdin、stdout、stderr, 这三个文件描述符默认默认指向 终端输入、终端输出,那么当命令需要获取输入的时候,它会去读取 fd0, 当要输出的时候它会像 fd1、fd2写入, 改变这些描述符指向的行为叫做重定向
<< 比较特殊, 表示继续沿用当前的标准输入, 只是当识别到指定的标识符后停止, 将接收到的内容作为 stdin
-
输出重定向符号
-
:覆盖写入文件
-
:追加写入文件
- 2>:错误输出写入文件
- &>:正确和错误输出统一写入到文件中
-
-
输入重定向符号
- <
- <<
判断命令
shell中提供了test、[、[[ 三种判断符号,可用于:
- 整数测试
- 字符串测试
- 文件测试
语法:
- test condition
- [ condition ]
- [[ condition ]]
注意:
-
中括号前后要有空格符
-
[ 和test是命令,只能使用自己支持的标志位,<、>、= 只能用来比较字符串
-
中括号内的变量,最好都是用引号括起来
-
[[ 更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则
根据程序是否正常执行(程序退出的状态)进行判断
exit:手动退出 shell 的命令 exit 10 返回 10 给 shell,返回值非 0 为不正常退出 $? 用于判读当前 shell 前一个进程是否正常退出(非 0 为不正常退出)
分支语句
- 语法1:
if condition ; then
程序段
elif condition ; then
程序段
else
程序段
fi


- 语法2:
case $变量 in:
"第一个变量内容")
程序段
;;
"第一个变量内容")
程序段
;;
*)
程序段
;;
esac

循环
-
while循环
while condition ; do 程序段; done
-
until循环
until condition ; do 程序段; done
-
for循环
for var in [words...]; do 程序段; done
函数
- 语法1:funcName(){ echo "abc"; }
- 语法2:function funcName(){echo "abc"; }
注意:
- shell自上而下执行,函数必须在使用前定义
- 函数获取变量和shell script 类似,1、$2...获取
- 函数内return仅仅表示函数执行状态,不代表函数执行结果
- 返回结果一般使用echo、printf,在外面使用$()、``获取结果
- 如何没有return,函数状态是上一条命令的执行状态,存储在$?中
模块化
模块化的原理是在当前shell内执行函数文件,方式:source [函数库的路径]
常用命令
| 命令 | 使用 |
| grep | 查找错误日志:grep -n "ERROR" -A3 -B3 cloudfun.log 统计次数:grep -n "ERROR" -c cloudfun.log |
| sort | 指定分隔符后以第三列进行排序:sort -t " " -k 3 |
| wc | 统计出现的行数、单词数、字符数 wc -lwm |
| head | 查看前十行:head -n 10 cloundfun.log |
| tail | 等待追加内容:tail -f -n 10 cloudfun.log |
| cut | 对数据行的内容进行处理 cut -d " " -f 3 |
| find | 文件和目录查找 |
| xargs | 参数处理 |
| which | 查找命令路径 |
执行过程和原理
执行
- shell脚本一般以
.sh结尾,也可以没有,这是一个约定;第一行需要指定用什么命令解释器来执行
#! /bin/bash
#! /usr/bin/env/bash
- 启动方式
#文件名运行
./filename.sh
#解释器运行
bash ./filename.sh
#source运行
source ./filename.sh
执行过程
-
字符解析
- 识别换行符、分号(;)做行的分割
- 识别命令连接符(|| && 管道)做命令的分割
- 识别空格、tab符,做命令和参数的分割
-
shell展开,例如{1..3}解析为1 2 3
-
重定向,将stdin、stdout、stderr的文件描述符进行指向变更
-
执行命令
- builtin直接执行
- 非builtin使用$PATH查找,然后启动子进程执行
-
收集状态并返回
bash 会以一些特殊字符作为分隔符,将文本进行分段解析。最主要是回车还有分号";"。在 bash 脚本中是以回车或者分号作为一行命令结束的标志。这就是第一层级的解析,将大段的命令行进行分段
符号拓展(使用各种方法,比如大括号 {} 、波浪符 ~ 、变量和参数的展开/替换、文件名展开),并最终执行命令(通过 shell 内置命令或外部命令)。
shell展开
-
大括号展开(Brace Expansion){...}
一般由三部分构成,前缀、一堆大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式{x..y[..incr]}
-
波浪号展开(Tilde Expansion)~
-
参数展开(Shell Parameter Expansion)
-
间接参数扩展${!parameter},其中引用的参数并不是parameter而是parameter的实际的值
-
参数长度${#parameter}
-
空参数处理
- ${parameter:-word} # 为空替换
- {parameter:=word} # 为空替换,并将值赋给parameter变量
- ${parameter:?word} # 为空报错
- ${parameter:+word} # 不为空替换
-
参数切片
- ${parameter:offset}
- ${parameter:offset:length}
-
参数部分删除
- ${parameter%word}# 最小限度从后面截取word
- ${parameter%%word} #最大限度从后面截取word
- ${parameter#word}# 最小限度从前面截取word
- ${parameter##word} #最大限度从前面截取word
-
-
命令替换(Command Substitution)
在子进程中执行命令,并用得到的结果 替换包裹的内容,形式上有两种:$(...)或
... -
数学计算(Arithmetic Expansion)$((..))
使用$(())包裹数学运算表达式,得到结果并替换
-
文件名展开(Filename Expansion)* ?[..]外壳文件名模式匹配
当有单词没有被引导包裹,且其中出现了 '*' , '?' , and '[' 字符,则shell会去按照正则匹配的方式查找文件名进行替换,如果没找到则保持不变
调试和前端集成
调试
-
普通log,使用echo、printf
-
使用set命令
| set配置 | 作用 | 补充 |
| -u | 遇到不存在的变量就会报错,并停止执行 | -o nounset |
| -x | 运行结果之前,先输出执行的那一行命令 | -o xtrace |
| -e | 只要发生错误,就终止运行 | -o errexit |
| -o pipefail | 管道符连链接的,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行 |
- vscode debug插件
VSCode配置
-
shellman:代码提示和自动补全
-
shellcheck:代码语法校验
-
shell-format:代码格式化
-
Bash Debug:支持单步调试
- 安装vscode插件
- 编写launch.json文件
- 升级bash到4.x以上版本
前端集成
-
node中通过exec、spawn调用shell命令
- exec 启动一个子 shell 进程执行传入的命令, 并且将执行结果保存在缓冲区中, 并且缓冲区是有大小限制的, 执行完毕通过回调函数返回
- spawn默认不使用shell, 而是直接启动子进程执行命令, 且会直接返回一个流对象,支持写入或者读取流数据, 这个在大数据量交互的场景比较适合
-
shell脚本中调用node命令
-
借助zx等库进行JavaScript、shell script的融合
- 借助shell完成系统操作,文件io、内存、磁盘系统状态查询等
- 借助nodejs完成应用层能力、网络IO、计算等