课程内容
- Shell基础概念
- 命令和语法
- 执行过程和原理
- 调试和前端集成
Shell基础概念
- 终端:获取用户输入、展示运算结果的硬件设备
- 终端模拟器:Mac Terminal、iTerm2等,关联虚拟tty的输入输出软件
- tty:teletypeWriter的简称,在Linux中是输入/输出环境
- Shell:command interpreter,处理来自终端模拟器的输入,解释执行之后输出结果给终端
- Bash:shell的一种具体实现
构成
语法和命令
自定义变量
系统环境变量
配置文件加载
source ~/.bashrc:使配置文本生效
运算符和引用
curl http://localhost:8080:客户端url通过命令行发送请求node server.js &:后台运行(关闭shell时运行停止)nohup node server.js &:后台运行挂起(关闭shell依旧执行)
管道
-
语法:
cmd1 | cmd3:将前一个命令的结果传递给后面的命令 -
要求:管道右侧的命令必须能接受标准输入才行,比如grep命令、ls、mv等不能直接使用,可以使用xargs预处理
-
注意:管道命令仅仅处理stdout,对于stderr会予以忽略,可以使用
set -o pipefail设置shell遇到管道错误退出 -
例子:
cat platform.access.log | grep ERRORnetstat -an | grep ESTABLISHED | wc -lfind . -maxdepth 1 -name "*.sh*" | xargs ls -l:查询.sh文件具体信息
重定向
- 输出重定向符号
>:覆盖写入文件>>:追加写入文件2>:错误输出写入文件&>:正确和错误输出统一写入到文件中
- 输入重定向符号
<<<
例子:
ls > file.txt:将ls命令的输出重定向到file.txt文件中,如果文件不存在则创建,如果文件已经存在则覆盖。ls >> file.txt:将ls命令的输出重定向到file.txt文件中,如果文件不存在则创建,如果文件已经存在则在文件末尾追加。ls 2> error.txt:将ls命令的错误输出重定向到error.txt文件中,如果文件不存在则创建,如果文件已经存在则覆盖。ls &> output.txt:将ls命令的输出和错误输出重定向到output.txt文件中,如果文件不存在则创建,如果文件已经存在则覆盖。sort < file.txt:将file.txt文件的内容作为sort命令的输入。cat << EOF:将下面的一段字符串作为cat命令的输入直到遇到EOF为止。
判断命令
shell中提供了test、[、[[三种判断符号,可用于:
- 整数测试
- 字符串测试
- 文件测试
语法:
test conditiontest命令用于检查某个条件是否成立。例如,test -e filename会检查文件filename是否存在。如果存在,则命令返回真(退出状态为 0);否则返回假(退出状态为非 0)。
[ condition ][命令实际上是test命令的另一种写法。它的语法与test命令相同,只是需要在最后添加一个]。例如,[ -e filename ]等同于test -e filename。
[[ condition ]][[命令是一种扩展的测试命令,它提供了比test和[更多的功能。例如,它支持模式匹配和正则表达式匹配。它的语法与[类似,只是需要在最后添加一个]]。例如,[[ -e filename ]]等同于[ -e filename ]。
注意:
- 中括号前后要有空格符
- [ 和test是命令,只能使用自己支持的标志位,<、>、=只能用来比较字符串
- 中括号内的变量,最好都是用引号括起来(eg.
[ "$name"=="hello"]) - [[ 更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则
这些命令通常用于 shell 脚本中的条件判断。例如:
if [ -e filename ]; then
echo "文件存在"
else
echo "文件不存在"
fi
复制代码
上面的脚本使用 [ 命令检查文件 filename 是否存在,然后根据检查结果执行不同的操作。
#!/bin/bash
# 整数测试
test $nl -eq $n2 # 测试 $nl 是否等于 $n2
test $nl -lt $n2 # 测试 $nl 是否小于 $n2
test $nl -gt $n2 # 测试 $nl 是否大于 $n2
# 字符串测试
test -z $str_a # 测试 $str_a 是否为空
test -n $str_a # 测试 $str_a 是否非空
test $str_a = $str_b # 测试 $str_a 是否等于 $str_b
# 文件测试
test -e /dmt && echo "exist" # 测试 /dmt 是否存在,并在存在时输出 "exist"
test -f /usr/bin/npm && echo "file exist" # 测试 /usr/bin/npm 是否存在,并在存在时输出
分支语句
if else语句 最简单的用法就是只使用 if 语句,它的语法格式为:
if condition
then
statement(s)
fi
复制代码
condition 是判断条件,如果 condition 成立(返回“真”),那么 then 后边的语句将会被执行;如果 condition 不成立(返回“假”),那么不会执行任何语句。注意,最后必须以 fi 来闭合。
如果有两个分支,就可以使用 if else 语句,它的格式为:
if condition
then
statement1
else
statement2
fi
复制代码
如果 condition 成立,那么 then 后边的 statement1 语句将会被执行;否则,执行 else 后边的 statement2 语句。
if elif else语句 Shell 支持任意数目的分支,当分支比较多时,可以使用 if elif else 结构,它的格式为:
if condition1
then
statement1
elif condition2
then
statement2
elif condition3
then
statement3
……
else
statementn
fi
复制代码
注意,if 和 elif 后边都得跟着 then。
case in语句 case in 语句的语法格式为:
case 变量 in
情况一)
statement1;;
情况二)
statement2;;
*)
default statement;;
esac
复制代码
循环
- for 循环 for 循环通常用于遍历一组数据,它的语法格式为:
for 变量 in 列表
do
命令序列
done
复制代码
其中,列表可以是一组数字、字符或字符串,也可以是一个命令的输出结果。for 循环会依次将列表中的每个元素赋值给变量,并执行命令序列。
- while 循环 while 循环用于在满足条件的情况下重复执行一段代码,它的语法格式为:
while 条件测试
do
命令序列
done
复制代码
其中,条件测试可以是一个命令或一组命令,也可以是一个逻辑表达式。当条件测试返回真时,while 循环会执行命令序列;当条件测试返回假时,while 循环结束。
- until 循环 until 循环与 while 循环类似,但它是在条件不满足的情况下重复执行一段代码,它的语法格式为:
until 条件测试
do
命令序列
done
复制代码
其中,条件测试可以是一个命令或一组命令,也可以是一个逻辑表达式。当条件测试返回假时,until 循环会执行命令序列;当条件测试返回真时,until 循环结束。
函数
函数的使用
#!/bin/bash
printName() {
if [ $# -lt 2 ]; then
echo "illegal parameter"
exit 1
fi
echo "firstname is :$1"
echo "lastname is :$2"
}
printName jacky chen
注意
- shell自上而下执行,函数必须在使用前定义
- 函数获取变量和shell script类似,0代表函数名,后续参数通过0代表函数名,后续参数通过1、$2..获取
- 函数内return仅仅表示函数执行状态,不代表函数执行结果
- 返回结果一般使用echo、printf,在外面使用$()、``获取结果
- 如果没有return,函数状态是上一条命令的执行状态,存储在$?中
示例
function test() { # 定义一个名为test的函数
local word="hello world" # 定义一个名为word的本地变量,值为"hello world"
echo $word # 输出word的值到标准输出
return 10 # 返回值为10
unset word # 没有执行到这里,因为前面已经执行了return语句
} # 函数定义结束
content=`test` # 执行test函数,并将输出赋值给变量content
echo "状态码:$?" # 打印上一条命令的状态码,应该是10
echo "执行结果:$content" # 打印变量content的值,即"hello world"
模块化
模块化的原理是在当前shell内执行函数文件,方式:
- source[函数库的路径]
常用命令
-A3 -B3:前3行,后3行
-f:读到文件末尾之后,不会停止命令的执行
-n:是一个选项,用于指定显示的行数。这里指定为10,即显示文件的最后10行
执行过程和原理
- shell脚本一般以.sh结尾,也可以没有,这是一个约定;第一行需要指定用什么命令解释器来执行
#! /bin/bash
#! /user/bin/env bash
- 启动shell的三种方式
#文件名运行(子进程执行)
./filename.sh
#解释器运行(子进程执行)
bash ./filename.sh
# source运行(当前进程中执行)
source ./filename.sh
-
字符解析
- 识别换行符、分号(;)做行的分割
- 识别命令连接符(||&&管道)做命令的分割
- 识别空格、tab符,做命令和参数的分割
-
shell展开,例如{1...3}解析为1 2 3
-
重定向,将stdin、stdout、stderr的文件描述符进行指向变更
-
执行命令
- builtin直接执行
- 非builtin使用$PATH查找,然后启动子进程执行
-
收集状态并返回给脚本
shell展开
Shell 展开是指在执行命令之前,Shell 预处理命令行中的各种特殊字符,将它们替换为实际的值或执行相应的操作
- 大括号展开(Brace Expansion):使用大括号
{}将一组字符串包含在命令行中,Shell 将展开大括号内的所有字符串,并将其作为命令行的一部分。例如:echo file{1..3}.txt将展开为file1.txt file2.txt file3.txt。
- 波浪号展开(Tilde Expansion):使用波浪号
~将用户名或路径名包含在命令行中,Shell 将展开波浪号并将其替换为相应的用户名或路径名。例如:cd ~/Documents将进入当前用户的 Documents 目录。
- 参数展开(Parameter Expansion):使用
$将变量名包含在命令行中,并使用一些特殊符号来修改变量的值或进行字符串操作。例如:${var:-default}表示如果变量$var未定义,则使用默认值default。
3.1 间接参数展开
3.2 参数长度
3.3 空参数处理
3.4 参数部分删除
- 命令替换(Command Substitution):使用反引号或
$()将一个命令包括起来,Shell 将执行该命令并将其输出作为命令行的一部分。例如:echo "The date is $(date)"将输出当前日期和时间。
-
数学计算(Arithmetic Expansion):使用
$(( ))将一个算术表达式包含在命令行中,Shell 将计算该表达式并将其替换为计算结果。例如:echo $((1+2))将输出3。 -
文件名展开(Filename Expansion):使用通配符
*、?、[]等来匹配文件名或路径名。Shell 将展开通配符并将其替换为匹配的文件名或路径名。例如:ls *.txt将列出当前目录下所有扩展名为.txt的文件。
调试和前端集成
调试的方法
- 普通log,使用echo、printf
- 使用set命令
- vscode debug插件
#! /bin/bash
#一般在最前面就进行配置
set -uxe -o pipefail
echo "hello world"
VSCode配置
-
shellman:代码提示和自动补全
-
shellcheck:代码语法校验
-
shell-format:代码格式化
-
Bash Debug:支持单步调试
- 安装vscode插件
- 编写launch.json文件
- 升级bash到4.x以上版本
前端集成
- node中通过exec、spawn调用shell命令
exec和spawn区别:
exec 函数会创建一个 shell 并在其中执行指定的命令。它会缓存整个输出,并在命令执行完毕后将其一次性返回给回调函数(使用 shell 执行命令并缓存整个输出)。
spawn 函数不会创建一个 shell,而是直接将命令和参数作为参数传递给子进程。它会立即返回一个 ChildProcess 对象(直接创建子进程并提供三个流属性,可以通过监听事件逐步处理输出)。
- shell脚本中调用node命令
-
借助zx等库进行javascript、shell script的融合
- 借助shell完成系统操作,文件io、内存、磁盘系统状态查看
- 借助nodejs完成应用层能力,网络io、计算等