0003.Shell 脚本和编程 | 青训营笔记
[TOC]
一、学习 shell的价值
- Linux 服务器的基本操作和管理
- 前端 Node.js服务的进程管理、问题排查、资源监控等运维操作
- 使用 shell编写TCE、SCM、Docker脚本,完成服务编译和部署
二、课程准备
- 一台安装了 linux系统的物理机或者云主机,可运行 shell 脚本
- 本地的 vscode 安装 Bash Debug插件,并升级 bash到4.x 以上
- "Npm全局安装 zx 依赖
三、Shell 基础概念
1.概念
- 终端:获取用户输入、展示运算结果的硬件设备。
- tty:teletypeWriter的简称 ,和终端等价,早期指电传印机,在linx 中是输入/输出环境。
- 终端模拟器:Mac Terminal、 iTerm2等,关联虚拟tty的输入输出软件。
- Shell:command interpreter,处理来自终端模拟器的输入,解释执行之后输出结果给终端。
- Bash:shell的一种具体实现。
2.发展
- Ken Thompson (来自贝尔实验室) 在 1971 年为 UNIX开发了第一个 shell, 称为 V6 shell
- Stephen Bourne 在贝尔实验室 为 V7 UNIX所开发的Bourne shell,即 sh
- 开源组织 GNU 为了取代 Boure shell 开发的Bourne-Again shell,即 Bash
3.构成
四、命令和语法
1.变量
| 类型 | 作用域 | 声明方式 | 规范 |
|---|---|---|---|
| 自定义变量 | 当前shell | = | 字符串、整型、浮点型、日期型 |
| 环境变量 | 当前shell及其子shell | export、declare -x | |
| 系统环境变量 | 所有shell | 启动加载 |
2.自定义变量
#!/bin/bash
#变量名=变量值(等号左右不能有空格)
page_size=1
page_num=2
#将命令复制给变量
_ls=ls
#将命令结果赋值给变量
file_list=$(ls -a)
#默认字符串,不会进行+运算
total=page_size*page_num
# 声明变量为整型
let total=page_size*page_num
declare -i total=page_size*page_num
# 导出环境变量
export total
declare -x total
declare [+/-] 选项 变量
- 给变量设定类型属性
+ 取消变量的类型属性
-a 将变量声明为数组类型
-i 将变量声明为整数型
-x 将变量声明为环境变量
-r 将变量声明为只读变量
-p 显示指定变量的被声明的类型
3.系统环境变量
| 变量名 | 含义 | 常见操作 |
|---|---|---|
| $0 | 当前shell名称/脚本名称 | $1等可以获取到传入参数 |
| $# | 传入脚本的参数数量 | if[$# -gt 1] |
| $* | 传入脚本的所有参数 | |
| $? | 上调命令执行的状态码 | if[$? -eq 0]; |
| $PS1 | 命令提示符 | export PS1="\u@\h\w>" |
| $HOME | 用户主文件夹 | cd~ |
| $PATH | 全局命令的搜索路径 | PATH=$PATH:[新增路径] |
4.配置文件加载
5.运算符和引用
6.管道
管道与管道|,作用是将前一个命令的结果传递给后面的命令 语法:cmd1 | cmd2
要求:管道右侧的命令必须能接受标准输入才行, 比如 grep 命令,Is、mv等不能直接使用,可以使用xargs 预处理
注意: 管道命令仅仅处理 stdout,对于stderr 会予以忽略, 可以使用 set -o pipefail 设置 shell 遇到管道错误退出
7.重定向
输出重定向符号
>:覆盖写入文件
>>: 追加写入文件
2>: 错误输出写入文件
&>: 正确和错误输出统一写入到文件中
输入重定向符号
<
<<
8.判断命令
shell 中提供了 test、[、[[三种判断符号,可用于:
- 整数测试
- 字符串测试
- 文件测试
语法:
- test condition
- [ condition ]
- [[ condition ]]
#整数测试
test $nl -eq $n2
test $nl -lt $n2
test $nl -qt $n2
#字符串测试
test -z $str_a
test -n $str_a
test $str_a = $str_b
#文件测试
test -e /dmt && echo "exist"
test -f /usr/bin/npm && echo "file exist"
注意:
- 中括号前后要有空格符
- [和test是命令,只能使用自己支持的标志位, <、>、= 只能用来比较字符串
- 中括号内的变量,最好都是用引号括起来
- [[更丰富,在整型比较中支持<、>、=,
- 在字符串比较中支持=~正则
9.分支语句
语法1:
if condition ; then 程序段 elif condition; then 程序段 esle 程序段 fi
语法2:
case $变量 in: "第一个变量内容") 程序段 ;; "第一个变量内容") 程序段 "" *) 程序段 "" esac
10.循环
- while循环 while condition ; do 程序段; done
- until循环 until condition ; do 程序段; done
- for循环 for var in [words...]; do 程序段; done
11.循环
- 语法一: funcName(){ echo "abc'; }
- 语法二: function funcName() { echo "abc"; }
注意:
shall自上而下执行,函数必须在使用前定义
函数获取变量和shell script类似,$0代表函数名,后续参数通过$1、$2...获取
函数内 return 仅仅表示函数执行状态,不代表函数执行结果
返回结果一般使用 echo、printf, 在外面使用$()、``获取结果
如果没有 return,函数状态是上一条命令的执行状态,存储在别中$?中
12.模块化
模块化的原理是在当前 shell内执行函数文件,方式: source [函数库的路径]
13.常用命令
| 命令 | 使用 |
|---|---|
| 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 cloudfun.log |
| tail | 等待追加内容:tail -f -n 10cloudfun.log |
| cut | 对数据行的内容进行处理 cut -d " " -f 3 |
| find | 文件和目录查找 |
| xargs | 参数处理 |
| which | 查找命令路径 |
五、执行过程和原理
1.执行
(1)shell脚本一般以s结尾,也可以没有,这是一个约定;第一行需要指定用什么命令解释器来执行 #! /bin/bash #! /usr/bin/env bash (2)启动方式 #文件名运行 ./filename.sh #解释器运行 bash ./filename.sh #source 运行 source ./filename.sh
2.执行过程
(1)字符解析
- 识别换行符、分号(:)做行的分割
- 识别命今连接符(||&&管道)做命令的分割
- 识别空格、tab符,做命令和参数的分割
(2)shell展开
例如{1..3)解析为123
(2.1)大括号号展开 (Brace Expansion) {...}
一般由三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式{x..y[..incr]}
#字符串序列
a(b,c,d)e=> abe ace ade
#表达式序列,(数字可以使用 incr调整增量,字母不行)
{1..5}-> 1 2 3 4 5
{1..5..2} > 1 3 5
{a..e} -> a b c d e
(2.2)波浪号展开 (Tilde Expansion) ~
#当前用户主日录
=> SHOME
-/foo =>$HONE/foo
#指定用户的主目录
-fred/foo => 用户fred的 $HOEM/foo
#当前工作日录
-+/foo => $PWD/foo
#上一个工作目录
~-/foo => ${$OLDPWD-'~-'}/foo
(2.3)参数展开 (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#ward} #最小限度从前面截取word
-
${parameter##word) #最大限度从前面截取word
(2.4)命令替换 (Command Substitution)
在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种: $(...)或`...`
(2.5)数学计算 (Arithmetic Expansion) $((..))
使用$(())包裹数学运算表达式,得到结果并替换
(2.6)文件名展开 (Filename Expansion) *?[..]外壳文件名模式匹配
当有单词没有被引号包裹,且其中出现了'*','?',and '['字符,则shell会去按照正则匹配的方式查找文件名进行替换,如果没找到则保持不变.
(3)重定向
将stdin、sdout、stderr的文件描述符进行指向变更
(4)执行命令
- builtin 直接执行
- 非 builtin 使用$PATH查找,然后启动子进程执行
(5)收集状态并返回
六、调试和前端集成
1.调试的方法
(1)普通log,使用echo、printf
#!/bin/sh
a=1
d=(12345)
echo $a
echo ${d[3]}
echo ${d[@]}
#输出
#1
#4
#1 2 3 4 5
(2)使用set命令
| set配置 | 作用 | 补充 |
|---|---|---|
| -u | 遇到不存在的变量就会报错,并停止执行 | -o nunset |
| -x | 运行结果之前,先输出执行的那一行命令 | -o xtrace |
| -e | 只要发生错误,就终止执行 | -o errexit |
| -o pipefail | 管道符链接的,只要一个子命今失败,整个管道命令就失败,脚本就会终止执行 |
(3)vscode debug插件
- shellman:代码提示和自动补全
- shellcheck:代码语法校验
- shell-format:代码格式化
- Bash Debug:支持单步调试 -安装 vscode 插件 -编写 launch.json文件 -升级 bash 到4.x以上版本
2.前端集成
- node中通过 exec. spawn 调用shell 命令
- shell脚本中调用node 命令
- 借助zx 等库进行iavascript、shell script 的融合 -借助shell 完成系统操作, 文件io、内存、磁盘系统状态查询等 -借助nodeis 完成应用层能力,网络io、计算等