0课程准备
-
linux环境
-
可以采用虚拟机+linux发行版的方式
- 虚拟机:VMware(网上资源多)
- linux发行版:centos,Ubuntu...(都可以国内有镜像网站,速度慢可以找热心网友的网盘分享)
-
也可以使用蓝桥云课
-
-
vscode安装bash debug插件
-
npm全局安装zx依赖
1为什么要学习shell
-
服务的编译和部署->编写tce、scm、docker脚本
-
前端node.js服务的进程管理、问题排查、资源监控等运维操作
2shell基础概念
2.1概念
- 终端:获取用户输入、展示运算结果的硬件设备->用户交互硬件
- tty:和终端等价,在linux中是输入/输出环境
- 终端模拟器:关联虚拟tty的输入输出软件
- shell:处理来自终端模拟器的输入,解释执行之后输出结果给终端
- bash:shell的一种具体实现
2.2发展
- shell顾名思义就是“壳”,操作系统上为了实现用户与内核交互的设计
- Thompson shell,为V6Unix编写了第一个shell,即/bin/sh
- Bourne shell
- 开源组织的GNU开发的Bash shell
2.3构成
-
shell具有两重性质
-
解释器(linux的核心思想:一切皆为文件)可以通过
type <command>尝试一下- bash内置命令
- gnu核心工作集
- 第三方库
-
内建命令实际上是 shell 程序的一部分,其中包含的是一些比较简单的 Linux 系统命令,这些命令是写在 bash 源码的 builtins 里面的,由 shell 程序识别并在 shell 程序内部完成运行,通常在 Linux 系统加载运行时 shell 就被加载并驻留在系统内存中。而且解析内部命令 shell 不需要创建子进程,因此其执行速度比外部命令快。比如:history、cd、exit 等等。
外部命令是 Linux 系统中的实用程序部分,因为实用程序的功能通常都比较强大,所以其包含的程序量也会很大,在系统加载时并不随系统一起被加载到内存中,而是在需要时才将其调入内存。虽然其不包含在 shell 中,但是其命令执行过程是由 shell 程序控制的。外部命令是在 Bash 之外额外安装的,通常放在/bin,/usr/bin,/sbin,/usr/sbin 等等。比如:ls、vi 等。
-
编程语言
-
变量
- 自定义变量
- 环境变量
- 系统环境变量
-
运算
- 逻辑运算符
- 算术运算符
-
语句
- 判断
- 分支
- 循环
-
函数
-
-
3shell语法和命令
3.1变量
| 类型 | 作用域 | 声明方式 | 规范 |
|---|---|---|---|
| 自定义变量 | 当前shell | = | 字符串、整型、浮点型、日期型 |
| 环境变量 | 当前shell及其子shell | export,declare -x | |
| 系统环境变量 | 所有shell | 启动加载 |
-
父子shell
-
父进程和子进程
-
3.2自定义变量
#!/bin/bash 先写解析器,没有用默认,默认的解析器可以从passwd文件中查看
# 变量名=变量值(等号左右不能有空格)
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
- 对于上述例子,可以在linux环境下的终端中尝试一下得到以下结果,由于默认为字符串,最初我们是将
page_size*page_num这个字符串赋给了total
-
对于declare变量的参数
-
-给变量设定类型属性 -
+取消变量的类型属性 -
-a将变量声明为数组类型 -
-i将变量声明为整型 -
-x将变量声明为环境变量 -
-r将变量声明为只读变量 -
-p显示指定变量的被声明的类型
-
-
数据类型
- String,只有一种数据类型,但是可以对其进行声明
-
变量取值
-
$变量名 ${变量名} #主要是为了区别,因为变量无需声明就可以使用
-
-
变量赋值
-
[let] var=value b=$a shiyanlou:~/ $ a=6 [19:32:56] shiyanlou:~/ $ echo $a [19:33:00] 6 shiyanlou:~/ $ echo ${b?} [19:33:03] zsh: b: parameter not set shiyanlou:~/ $ echo ${b?"no var"} [19:33:13] zsh: b: "no var" shiyanlou:~/ $ echo ${b:="tt"} [19:33:26] tt shiyanlou:~/ $ echo $b [19:33:39] tt shiyanlou:~/ $ echo ${a:="tt"} [19:33:44] 6 shiyanlou:~/ $ echo ${c:-"tt"} [19:34:00] tt shiyanlou:~/ $ echo $c [19:34:18] shiyanlou:~/ $ echo ${c:+"tt"} [19:34:26] shiyanlou:~/ $ echo $c [19:34:41] shiyanlou:~/ $ echo ${a:+"tt"} [19:34:45] tt shiyanlou:~/ $ echo $a [19:34:56] 6 shiyanlou:~/ $ echo ${a:+"temp"} [19:34:59] temp '#'从左匹配
-
-
清空变量
-
readonly var
-
3.3系统环境变量
| 变量名 | 含义 | 常见操作 |
|---|---|---|
| $0 | 当前shell名称/脚本名称 | 2等可以获取到传入参数 |
| $# | 当前shell及其子shell | if[$# -gt 1] |
| $* | 传入脚本的所有参数 | |
| $? | 上条命令执行的状态码 | If [$? -eq 0] |
| $PS1 | 命令提示符 | Export PS1="\u@\h \w>" |
| $HOME | 用户主文件夹 | Cd ~ |
| $PATH | 全局命令的搜索路径 | PATH=$PATH:[新增路径] |
| $USER | 用户 |
3.4位置变量
3.5配置文件加载
-
login shell
-
non-login shell
- 交互式的
- 非交互式的
-
加载过程
-
(login shell)->(/etc/profile)->(first of:~/bash_profile ~/bash_login
/.profile)->(./bashrc)->(/etc/bashrc)
-
3.6运算符和引用
3.7管道
-
管道与管道符
|,作用是将前一个命令的结果传递给后面的命令- 语法: cmd | cmd2
- 注意:管道右侧的命令必须能接受标准输入才行,ls、mv等不能直接使用,可以使用xargs预处理
xargs ls -l-
-
stderr会忽略,可以在脚本当中加上
set -o pipefail进行设置,管道在错误的时候退出
-
3.8重定向
- 输出重定向符号
>:覆写写入文件
>>:追加写入文件
2>:错误输出写入文件
&>:正确和错误输出统一写入到文件当中
- 输入重定向符号
<
<<
3.9判断命令
-
shell中提供了
test`[\[[`三种判断符号,可用于整数测试、字符串测试、文件测试 -
语法
-
test condition -
[ condition ]- 前后要有空格符
- 中括号内的变量最好都用括号括起来
test和[是命令,只能使用自己支持的标志位,<>=只能用来比较字符串
-
[[ condition ]]- 在整型比较中支持
<>=,在字符串比较中支持=~正则
- 在整型比较中支持
-
-
一些例子
-
#!/bin/bash #整数测试 test $nl -eq $n2 #等于 test $nl -lt $n2 #小于 test $nl -gt $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" #文件是否普通且存在
-
3.10分支语句
-
语法1
-
if condition;then 程序段 elif condition;then 程序段 else 程序段 fi #例子 #!/bin/bash level=0 if [ -n "$level"];then #先判断是否存在 if[ "$level" ==0];then prefix=ERROR elif[ "$level" ==1];then prefix=INFO else echo "log level not supported" fi fi echo "[${prefix}] $message"
-
-
语法二
-
case $变量 in: "第一个变量内容") 程序段 ;; "第二个变量内容") 程序段 ;; *)#默认分支表达式 程序段 ;; esac
-
3.11循环语句
-
while循环
-
while condition; do 程序段; done #!/bin/bash let num=0 while [ $num -lt 10]; do echo "current idx: $num" ((num++)) done
-
-
until循环 # 条件成立跳出循环
-
until condition; do 程序段; done #!/bin/bash let num=0 until [ $num -gt 10]; do echo "current idx: $num" ((num++)) done
-
-
for循环
-
for var in [words...]; do 程序段; done #!/bin/bash # 对列表进行循环 for foo in a b c do echo $foo done # 数值方式循环 for((i=0;i<10;i++)) do echo $i done
-
3.12函数
语法1
funcName(){echo"abc";}
语法二
function funcName(){echo"abc";}
函数调用
funcName var1 var2
-
注意
-
函数要在使用前定义
-
函数获取变量和shell script类似,1$2...获取
-
函数内return仅仅表示函数执行状态,不代表函数执行结果
-
返回结果一般使用echo、printf,在外面使用$() 、" 获取结果//去打印ji
-
#!/bin/sh function test(){ local word="hello world" # local,理解为局部变量 echo $word return 10 unset word } content='test' echo "状态码:$?" #$?上条命令执行的状态码 echo "执行结果:$content"
-
-
如果没有return,函数状态是上一条命令的执行状态,存储在$?当中
-
3.13模块化
- 在当前shell内执行函数文件
source [函数库的路径]
3.14常用命令
- 例子
#筛选日志当中的错误信息
cat cloudfun.log | grep -n "ERROR" #grep本质是去匹配字符串 -n:显示匹配行及 行号
#筛选日志当中的错误信息并按照时间顺序排序
cat cloudfun.log | grep -n "ERROR" | sort -t " " -k 3 #将信息依照空格分割后,以第三个列的值进行排序,需要注意的是,对于行的判定,一定是具有换行符才会被称作一行,和我们显示所看到的没有太大的关联
#查看错误日志信息的上下文
grep -n "ERROR" cloudfun.log -A3 -B3 #-A:After -B:Before
#tail/head
tail -n 10 filename
# -f参数:循环读取
4执行原理
4.1执行
- shell脚本一般以
.sh结尾,也可以没有,脚本中第一行指定解释器
#!/bin/bash
#!/usr/bin/env bash
- 启动方式
# 文件名运行,有执行权限
./filename.sh
# 解释器运行
bash ./filename.sh
# source运行
source ./filename.sh
4.2执行过程(shell是一门解释型语言)
-
字符解析
- 识别换行符、分号:做行的分割
- 识别命令连接符(|| && 管道):做命令的分割
- 识别空格、tab符,做命令和参数的分割
-
shell展开
-
大括号展开:
{1..3}解析为1 2 3- 一般由三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式
{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
- 一般由三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式
-
波浪号展开(
~)cd ~其实就是进入当前用户主目录-
# 当前用户主目录 ~ => $HOME ~/foo => $HOME/foo # 指定用户的主目录 ~fred/foo => 用户fred的 $HOME/foo # 当前工作目录 ~+foo => $PWD/foo # 上一个工作目录 ~-/foo => ${$OLDPWD-'~-'}/foo
-
参数展开
- 间接参数扩展
${!parameter},其中引用的参数并不是parameter而是parameter的实际值 -
parameter="var" var="hello" echo ${!parameter} #输出为 hello - 参数长度
${#parameter} -
par=cd echo ${#par} #output 2 #length - 空参数处理
-
a=1 ${parameter:-word}#为空替换 echo ${a:-word}# 1 echo ${b:-word}# word ${parameter:=word}#为空替换并赋值,前面的替换并没有修改值,有点类似于选择 echo ${par:=word}# word echo ${par:-hello}# word ${par:+word}# foo - 参数切片
-
${parameter:offset} ${parameter:length} - 参数部分删除(掐头去尾的操作)
-
str=abcdefg ${parameter%word}# 最小限度从后面截取word ${parameter%%word}# 最大限度从后面截取word ${parameter#word}# 最小限度从前面截取word ${parameter##word}# 最大限度从后面截取word
- 间接参数扩展
-
命令替换
- 在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种
$(...)或`... ```` ` -
#!/bin/bash echo $(whoimi) foo(){ echo "asdasd" } a='foo'
- 在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种
-
数学计算(
$((..)))-
#!/bin/bash echo $((1+2)) # 3
-
-
文件展开(
*?[..]外壳文件名模式匹配)- 当有单词没有被引号包裹,且出现了 * ? [字符,会按照正则匹配的方式查找文件名进行替换
-
$ echo D* # 输出当前目录下所有以D字母开头的目录、文件
-
-
重定向,将
stdin、stdout、stderr的文件描述符进行指向变更 -
执行命令
- builtin直接执行内置命令
- 外部命令使用
$PATH查找,然后启动子进程执行
-
手机状态并返回
5.调试和前端集成
5.1调试
-
普通log,使用echo、printf
-
使用set命令
- 一般在最开头的时候进行相关的设置
-
#!/bin/bash set -uxe -o pipefail echo "hello"
-
Vscode debug插件及配置
-
shellman:代码提示和自动补全
-
shellcheck:代码语法校验shell-format:代码格式化
-
Bash debug:支持单步调试
-
安装vscode插件
-
编写launch.json文件
-
升级bash到4.x以上版本
-
-
5.2前端集成
-
node中通过exec、spawn调用shell命令
- exec执行一个子shell运行传入的命令,先放在缓冲区,200kb
- spawn不会有子进程,拥有流对象,适合
-
shell脚本中调用node命令
-
#!/bin/bash set -e node ./exec.js echo 'success'
-
-
借助zx等库进行js、shell script的融合
-
借助shell完成系统操作,文件io、内存、磁盘系统状态查询
-
借助nodejs完成应用层能力,网络io、计算等
-
#!/usr/bin/env zx
语法$'command'