03- shell 脚本和编程 | 青训营笔记

117 阅读14分钟

00 课程介绍

  • 学习shell的价值
    • Linux 服务器的基本操作与管理
    • 前端 Node.js 服务的进程管理、问题排查、资源监控等运维操作
    • 使用shell编写 TCE SCM Docker 脚本、完成服务编译与部署(字节的很多中台服务)
SCM 通常指的是“Software Configuration Management”,即软件配置管理。它是在软件开发过程中对软件进行版本控制、变更管理、构建管理、发布管理、问题管理等管理活动的统称。

TCE 的概念好像在不同的场景下区别较大,暂时不表

01 Shell 基本概念

相关概念

  • shell的发展经历了 物理终端-软件终端 - 终端模拟器- shell几个阶段

  • 终端:获取用户输入,展示运算结果的硬件设备

  • tty:早期电传打印机,在Linux中式 输入/输出 环境

  • 终端模拟器:关联虚拟tty的输入输出软件 MACterminal,ITERM2

  • Shell :command interpreter ,处理来自终端模拟器的输入,解释执行之后输出结果给终端

  • Bash:Shell的一种实现

  • 发展历程

    • V6 Shell 贝尔实验室
    • sh 贝尔实验室
    • Bash GNU
    • shell的相关版本
    • Pasted image 20230415235305.png

构成

  • 既是一种命令解释器,也是一种语言。
  • Pasted image 20230415235519.png
  • 以上是shell的一个组成表示
    • 其中解释器部分是值得注意的,有一些命令是 最早的bash就有的,有一些是 GNU 添加的,有一些是后来安装的第三方库

02 命令与语法

【语法】变量

  • shell 变量分类及声明方法
    • Pasted image 20230415235736.png
  • 关于父子 Shell 的解释说明
flowchart LR
id1["当前shell 父进程"]-.->|暂停|id2["继续下一条命令"]
id1-->id3["被父进程触发的shell 子进程"]-->|exit|id2

自定义变量

# 变量名=变量值,不能有空格,会被当成是一个命令
page_size=1


# 将命令赋值给变量
_ls=ls

# 默认字符串类型,不会进行加减运算
total=num1+num2
# 必须要将变量显式声明为整形
# 使用let 或者 declare -i 显式声明为整数
let total=num1+num2
declare -i total=num1+num2

# 导出环境变量
export total 
declare -x total

环境变量

  • shell 脚本中,子进程被创建时,会复制父进程的环境变量列表,但是相当于一份拷贝,不会改变父进程中的值。
  • 如果需要在子进程中直接访问父进程的变量,而不只是拷贝,需要导出环境变量(或者其他进程中
  • #注意
导出环境变量在 shell 编程中非常常见,用于传递参数、设置系统变量等。要 #注意 ,导出的变量只在当前 shell 会话中有效,一旦退出该会话,环境变量就会失效。如果希望永久保存环境变量,可以将其添加到 shell 配置文件中,例如 `~/.bashrc` 或 `~/.bash_profile`。

declare 定义变量类型与属性

# declare 相关参数得说明
1.  -r:设置变量为只读属性,即不能被修改。
2.  -i:将变量设置为整数类型,即只能存储整数值。
3.  -a:将变量设置为数组类型,即可以存储多个值。
4.  -f:将变量设置为函数类型,即可以存储函数代码。
5.  -x:将变量设置为环境变量,可以在 Shell 脚本中传递给子进程。
除了以上属性,declare 还可以用来设置变量的作用域。如果在函数内使用

系统环境变量

  • 以下是Linux-shell编程中常见的系统环境变量
  • Pasted image 20230416001949.png
# 显示当前用户目录
echo $HOME


echo $PS1
# 显示用户名 主机名 目录 (u\h\w)
# 因为我使用的是zsh,所以打印出来一堆奇怪的东西,看起来像是正则表达式

echo $USER
#显示用户名

echo $PATH
# 是PATH中的环境变量,是系统启动时,在默认环境变量配置文件中读取并加载的,如果需要可以在配置文件中进行更改
# 打印出来的值以冒号进行分割,系统在执行命令时,从左到右进行查找路径

  • 关于 PS1的说明
    • PS1 是提示符号,即当前终端每次输入命令之前的内容
    • echo $PS1 命令会打印出当前 shell 的提示符(prompt)的字符串。提示符是 shell 命令行中出现的文本,通常包含有用的信息,如用户名、主机名、当前工作目录等。提示符字符串可以通过修改 PS1 环境变量来定制。
    • 在大多数 Unix 和类 Unix 操作系统中,默认的提示符是 $ 符号。在 Bash shell 中,可以使用 PS1 环境变量来自定义提示符。例如,可以将 PS1 设置为以下值:
PS1="\u@\h:\w$ "
	- 这里,`\u` 表示当前用户名,`\h` 表示主机名,`\w` 表示当前工作目录。将这个字符串赋值给 `PS1` 环境变量后,命令行提示符就会变成类似于以下内容:
username@hostname:/path/to/current/directory $
  • 如果在 shell 命令行中运行 echo $PS1 命令,将会打印出定义的提示符字符串,例如:
\u@\h:\w$
  • 这个字符串是由 PS1 环境变量定义的,可根据需要进行自定义。

shell 变量-菜鸟

  • 感觉上面介绍的太简单了,参考 Shell 变量 | 菜鸟教程 (runoob.com) 再多看一些。
  • 变量定义与使用
    • 定义时,不加$
    • 使用时,加上 $
    • {}在需要区分变量边界时使用
  • 只读变量
    • 定义时在变量名前面加上readonly
  • 删除变量
    • 使用 unset可以删除变量,删除后不能再次使用
    • 只读变量不能删除
  • 变量类型
    • 局部变量(脚本中定义的)
    • 环境变量(前面所说的环境变量,在shell中及子进程中使用)
    • shell 变量(shell程序中预定义的,前文所说的系统环境变量,比如 $PATH $HOME $PS1等等)
  • shell字符串
    • 建议使用双引号(可以在内部使用变量,可以出现转义字符)
    • 单引号中的变量当作字符
    • 字符串拼接
    • 字符串长度
      • len=${#string_name} 获取整个字符串长度
      • len=${#string_name[0]} 获取的还是整个字符串的长度, #注意 这里要和后面的数组长度获取区分开来,数组若是使用下标,获取的是元素长度
    • 提取子字符串
    • 查找字符串

shell 数组-菜鸟

  • shell数组不支持多维数组,支持一维
  • 数组定义
    • 数组的定义 array=(v0 v1 v2) #注意 之间使用空格而不是逗号,也可以使用换行分割
    • 类似于JS,不限制数组大小,可以随意写 array[100]=200 这种,不会出现越界
  • 数组访问
    • 访问单个元素 ${array[0]} 建议加上{}
    • 访问全部元素 ${array[@]} 使用@符号
  • 获取数组长度
    • len=${#array_name[@]} 获取数组长度
    • len=${#array_name[*]} 获取数组长度
    • 获取单个元素长度
# 以下是关于字符串和数组的一个实例
#!/bin/bash

arr=("this is a string" "this is 2nd" 123)
str="string string"
for ele in ${arr[@]}
do 
	echo ${ele}
done

echo "#arr = ${#arr}"
echo "#arr[0] = ${#arr[0]}"
echo "#arr[@] = ${#arr[@]}"
echo "#arr[*] = ${#arr[*]}"
echo "#str len = ${#str}"
echo "#str[0] len=${#str[0]}"

# 输出
this
is
a
string
this
is
2nd
123
#arr = 16
#arr[0] = 16
#arr[@] = 3
#arr[*] = 3
#str len = 13
#str[0] len=13

#注意

- 上面的例子说明
	- 出现过的问题
		- 在for循环in后面 没有加上`$arr` ,直接写`arr`
		- 在循环体内访问ele,需要写 `$ele`
		- 在 for循环中尝试访问整个数组,需要 `${arr[@]}` 注意不加{},`arr[@]`会被当成一个整体去找有没有叫这个的变量,所以最终会被当成一个字符串
	- 输出分析
		- 1
	- 总结
		- 尽可能使用变量时全部加上{},以免出现未知的错误
		- 盲猜:在bash中,类似于python,所有的变量名本质上是一个类C的指针,所以打印arr长度会出现第一个元素的长度(因为arr是一个字符串具有一个截至符号),与打印 `arr[0]` 长度是一样的
		- 而string类型的访问也是直接使用名称进行访问,如果打印 `str[0]` 的长度,出现的还是str的长度,原因就是:bash中不区分这个变量到底是什么类型,而是当成一个指针,访问到遇到一个截至符号(数组中为了区分一个元素与下一个元素可能还有其他符号用于分隔)
		- 如果我在脚本中打印 `str[3]` ,这当然是一个越界访问,bash的输出结果是 ` ` ,即没有输出。但是并不报错,意味着这很可能只是那一块内存空间没有存值,这将导致一种风险。
		- 以上内容只是根据个人猜想和实验出的结果,具体bash实现中的内存与变量管理具体是什么样的应该要看源码或者文档。

关联数组

  • 本质上就是一个字典
  • 实例
declare -A array_name=(["taobao"]="www.taobao.com" ["geogle"]="www.geogle.com" ["cainiao"]="www.runoob.com")
  • 这种写法又和 mermaid 中的容器的概念很类似
  • 可以使用python中字典的访问方式进行访问

shell 注释-菜鸟

  • 单行注释 # 我感觉还是使用单行注释比较好
  • 多行注释
:<<EOF
this is line 1
this is line 2
EOF

# EOF 可以使用其他符号
# 突然想不起来自己要写什么了……,留着之后想起来了写
  • 上面多行注释的例子 参考后文 [[03- shell 脚本和编程#^85db14]]

【命令】shell 加载过程

  • shell分类

    • 登录式shell
    • 非登录式shell
      • 交互式
        • 在终端登录用户输入
      • 非交互式
        • sh ./xxx.sh 在子进程中执行,不进行交互
  • shell 加载过程 Pasted image 20230416161844.png

  • 加载文件生效方法

source 修改的加载文件

【语法】运算符和引用

Pasted image 20230416162216.png

  • 关于 后台运行的说明
# 执行命令,占据当前终端,使用 ctrl C 时强制退出
cmd
# 执行命令,不占据当前终端,使用ctrl c时不退出,但是关闭当前终端时,仍然退出进程
cmd &
# 执行命令,不占据当前终端,使用ctrl c 时不退出,但是关闭当前终端时,不退出进程
nohup cmd &

【命令】管道

  • 将前一个命令的结果传递给后一个命令作为输入
  • 语法 : cmd1 | cmd2
  • 要求:管道右侧的命令必须可以接受 标准输入,比如 grep命令,而 ls mv 等不能接受,可以使用 xargs 命令进行预处理
  • #注意 :管道只能处理标准输出 stadout ,但是对于 stderr 会予以忽略,可以使用 set -o pipefall 设置遇到管道错误时退出
# 寻找一层目录下的sh 文件,然后将结果传递给 ls 输出权限等的信息
find . -maxdepth 1 -name "*.sh" | ls -l
# 输出一大堆的文件和文件夹的信息,原因是,ls不能接收前面的输入

find . -maxdepth 1 -name "*.sh" | xargs ls -l
# 输出查找到的文件的详细信息
  • 上述原因是:ls不能接受标准输入,xargs 将标准输入转化为参数列表的形式传递给后面的指令
  • #注意 :上述示例中,-maxdepth 1 应该写在其他参数的前面,要不然会有warning

【命令】重定向

  • 下图中,左侧表示 标准输出、标准输入、标准错误的文件描述符,以及在交互式shell中指向的文件
  • 右侧表示 常见的输出 输入重定向符号 Pasted image 20230416164029.png
  • 下图中,左侧表示 重定向符号执行之后的作用 Pasted image 20230416164110.png
  • 示例
# 
ls -l nodejs >> list.txt 2>&1


cat list.txt # 查看内容
  • wc 命令的说明
    • 表示统计文件中 字节数,单词数,行数
    • -l 表示统计行数
    • -c 统计字节数
    • -m 统计字符数
    • -W 统计单词数
  • wc -l <<EOF 表示 将多行文本作为标准输入传入给wc命令,并计算这些文本中的行数
    • EOF 貌似不表示一个文件流,为什么可以作为输入重定向中的输入流?
    • EOF 是一个自定义的终止符号,用于指示输入的结束。当 shell 遇到 <<EOF 时,它会将输入标准切换到“Here Document”模式,读取后续的输入作为文本,并一直读取输入,直到遇到 EOF 为止。
    • 因此,<<EOF 实际上是将输入重定向到一个指定终止符号之前的所有文本。在 shell 中,可以使用任何有效的终止符号(例如 EOFENDSTOP 等)来指示输入的结束。
    • #注意 :上面的EOF 是一个自定义的符号,也就是说可以换成任何符号
wc -l <<EOF
EOF
# 上面的EOF 换成S STOP END 等的都是可以的
# 因为markdown中解析文档这部分按照shell解析,所以这部分如果去掉第二行的EOF,这里会显示为在obsidian中绿色的命令中文档的颜色

shell 输入输出重定向-菜鸟

command << delimiter
    document
delimiter
  • /dev/null 文件
    • 不想显示结果的时候重定向到这里
    • 2> 是一体的时候才表示错误输出

【语法】判断命令

  • shell中提供了 test、[、[[ 三种判断符号,可以用于
    • 整数测试
    • 字符串测试
    • 文件测试
  • 语法
test condition
[ condition ]
[[ condition ]]
  • 示例
#!/bin/bash

# 整数测试
test $n1 -eq $n2
test $n1 -lt $n2
test $n1 -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" # 文件存在并且是一个普通文件

  • #注意 :
    • 中括号前后要有空格符号
    • [和test 是命令,只能使用自己支持的标志位
    • < > =只能用来比较字符串
    • 中括号内的变量最好使用引号括起来
    • [[更加丰富,在整形中支持< > =,在字符串比较中支持 =~ 正则
  • 关于变量加引号的说明示例
#!/bin/bash

name="hello world"

[ $name == "hello" ]
# 相当于 
# [ hello world == "hello" ]
# 会出现“参数太多”的错误,但是由于bash属于脚本编程,如果在后面加上 || echo "no" 还是会打印出来 no
# 在这个地方的变量使用,更像是 C中的宏定义展开

# 上述错误代码应该修改为
[ "$name" == "hello" ]

shell 基本运算符-菜鸟

  • 这部分对应了上文中 [[03- shell 脚本和编程#【语法】运算符和引用]] 和 [[03- shell 脚本和编程#【语法】判断命令]] 中的一部分——逻辑运算符
  • Shell 基本运算符 | 菜鸟教程 (runoob.com) 中对于基本运算符罗列比较详细
  • 算术运算符
    • 原生的bash不支持算术运算符直接进行数学运算,需要使用expr
  • 关系运算符
  • 布尔运算符
    • #注意 与逻辑运算符不同
    • 布尔运算符用于对条件进行取反、或运算、与运算
  • 逻辑运算符
    • 用于对 条件语句中执行逻辑判断与流程控制
# 布尔运算例子
! true   # false
true -o false   # true
true -a false   # false

# 逻辑运算例子
if [ -f file.txt ] && [ -r file.txt ]; then
    echo "file.txt exists and readable."
fi

if [ -f file.txt ] || [ -f file2.txt ]; then
    echo "file.txt or file2.txt exists."
fi
  • 对于逻辑与布尔运用上的区别 #注意
    • Pasted image 20230416233020.png
    • 上述图像中 -o 用在 [] 之内,|| 用在 [] [] 之间
  • 字符串运算符
  • 文件测试运算符
    • 检测文件是什么或者具有什么属性
-b file # 块文件
-c file # 字符设备文件
-d file # 目录
-f file # 普通文件,不是目录 不是设备文件
-g file # 文件是否设置SGID位
-k file # 文件是否设置 sticky bit
-u file # 文件是否设置SUID位
-r file # 文件是否可读
-w file # 文件是否可写
-x file # 可执行
-s file # 空
-e file # 是否存在
-S file # 文件是不是socket
-L file # 是否存在且是一个符号链接
……
  • 补充内容
字符设备文件(Character Device File)是一种特殊类型的设备文件,用于与字符设备(如终端、键盘、鼠标等)进行交互。与字符设备相对应的是块设备,比如硬盘等。
在 Unix/Linux 系统中,所有设备都被视为文件,这种设备文件通常分为两类:字符设备文件和块设备文件。字符设备文件主要用于对字符设备进行访问和控制,它们不需要进行块数据的读写,而是以流的形式进行输入和输出。
字符设备文件通常具有以下特点:
1.  以字符为单位进行访问:字符设备文件通过一个字符流和用户进行交互,每次只能读写一个字符。
2.  没有固定的块大小:字符设备文件不需要进行块数据的读写,因此没有固定的块大小。
3.  不支持随机访问:由于字符设备文件是按字符流进行读写的,因此不支持随机访问,只能按照顺序进行读写
4.  可以异步访问:由于字符设备文件中的数据没有固定的块大小,因此可以进行异步访问,即在读写的过程中可以同时执行其操作。
常见的字符设备文件包括:
-   终端设备文件(/dev/tty)
-   控制台设备文件(/dev/console)
-   键盘设备文件(/dev/keyboard)
-   鼠标设备文件(/dev/mouse)
总之,字符设备文件是一种特殊的设备文件类型,用于与字符设备进行交互和控制。它们以字符为单位进行访问,没有固定的块大小,不支持随机访问,可以进行异步访问。

块设备文件(Block Device File)是一种特殊类型的设备文件,用于与块设备(如硬盘、U盘等)进行交互。与块设备相对应的是字符设备,如终端、键盘等。
在 Unix/Linux 系统中,所有设备都被视为文件,这种设备文件通常分为两类:字符设备文件和块设备文件。块设备文件主要用于对块设备进行访问和控制,它们需要以固定块大小进行读写,而不是单个字符流。
块设备文件通常具有以下特点:
1.  以块为单位进行访问:块设备文件通过一个块为单位进行读写操作,每个块的大小通常是512字节或4KB,而不是单个字符。
2.  有固定的块大小:块设备文件必须以固定的块大小进行读写操作。
3.  支持随机访问:由于块设备文件是以固定块大小进行读写的,因此支持随机访问,可以在任意位置读写数据。
4.  不支持异步访问:由于块设备文件需要进行块数据的读写,因此不支持异步访问。
常见的块设备文件包括:
-   硬盘设备文件(如/dev/sda)
-   U 盘设备文件(如/dev/sdb)
-   CD-ROM 设备文件(如/dev/sr0)
总之,块设备文件是一种特殊的设备文件类型,用于与块设备进行交互和控制。它们以块为单位进行访问,有固定的块大小,支持随机访问,不支持异步访问。

SGID 位是指 Set Group ID 的缩写,是 Unix/Linux 系统中一种文件权限机制。当一个文件的 SGID 位被设置时,文件被执行时会继承其所属组的权限,而不是执行者的组权限。

SGID 位是指 Set Group ID 的缩写,是 Unix/Linux 系统中一种文件权限机制。当一个文件的 SGID 位被设置时,文件被执行时会继承其所属组的权限,而不是执行者的组权限。

Sticky bit 是指粘滞位,是 Unix/Linux 操作系统的一种文件权限机制。它可以用在一个目录上,用于控制该目录下的文件对于普通用户的删除权限。
当一个目录设置了 sticky bit 后,即使该目录下的文件对于用户来说有写权限,但是用户也不能删除该目录下的其他用户创建的文件,只能删除自己创建的文件。这个机制在多用户共享目录时非常有用,可以防止其他用户误删某个用户的文件。
只有目录内文件的所有者或者root才可以删除或移动该文件。如果不为目录设置粘滞位,任何具有该目录写和执行权限的用户都可以删除和移动其中的文件。在我们系统中,粘滞位一般用于/tmp目录,以防止普通用户删除或移动其他用户的文件。
一个目录具有粘滞位,则在other的X位会表现为 t,或者T.大小写的区别在于,原来x位上有x权限,有了粘滞位则表现为t.否则,表现为T。

【语法】分支

Pasted image 20230416233720.png

  • 上图中给出了分支语句的标准模板
#!/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
message="end"

echo "[${prefix}] $message"
  • case语句实例代码
    • #注意 最后的 *) 实际上是C中的default,加上是好的规范的
#!/bin/bash

name=bob

case $name in 
	"nick")
		echo "hi nick"
	;;
	"join")
		echo "my name is join"
	;;
	*)
		echo "404"
	;;
esac
  • 多条件判断的标注实例 Pasted image 20230416234741.png
  • 注意 -o 与 || 用法上的区别 ,主要是中括号

【语法】循环

Pasted image 20230416235326.png

  • while循环
let num=0
while [ $num -lt 10 ];
do 
	echo "current idx: $num"
	((num++))
done
  • 上面代码中let与(())的说明
let 用于进行一些数值运算的赋值,比如操作数有立即数的情况,注意等号两侧不能有空格

(())用于进行数值之间的五种算术运算,自增自减运算,以及支持比较运算符,括号内不需要使用$ 符号

总之以上两种基本都是在需要直接使用数字作为操作数的情况下使用(应该
  • until 循环
let num=0

until [ $num -gt 10 ];
do 
	echo "current idx: $num"
	((num++))
done
  • for循环
  • 对列表进行循环 与 对数值方式进行循环 两种模板
# 列表 有点像python的迭代式
for foo in a b c
do
	echo $foo
done

# 数值方式循环 和C语言基本一样,需要注意 两层括号,不需要 int
for((i=0;i<10;i++))
do
	echo $i
done

shell 流程控制-菜鸟

分支

  • 以下是if三种语法结构
# if
if condition
then 
	cmd
fi
# 写成一行 用;表示分割
if condition; then cmd;fi

# 个人感觉写成这样子比较好
if condition; then
	 cmd
elif condition; then
	cmd
else
	cmd
fi


# if else
if condition
then 
	cmd
else
	cmd
fi
#

# if elseif else
if contidion
then
	cmd
elif
then 
	cmd
else
	cmd
fi

  • case esac 这个语句有些丑
case <value> in 
mode1)
	cmd1
	cmd2
	;; # 在case中表示break
mode2)

	;;
esac

循环语句

  • 注意变量使用时尽量加上 {},一定要有$
  • for 循环
for var in item1 item2……
do
	cmd
done
# 写成一行
for var in items;do cmd1;cmd2 done;
  • while循环
while condition
do
	cmd
done


# while 循环可以用作读取键盘信息
# 让用户使用强制退出退出程序即可
# 以下是一段例子,注意不需要用无限循环,直接就能不断在终端中读入
echo '按下 <CTRL-D> 退出'  
echo -n '输入你最喜欢的网站名: '  
while read FILM  
do  
    echo "是的!$FILM 是一个好网站"  
done



# while死循环
while :
do
	cmd
done
# 或者
while true
do 
	cmd
done
  • until 循环
    • 感觉用不到
  • 跳出循环
    • break
    • continue
  • 补充说明关于终止终端中的进程
在 Linux 终端中,当运行一个程序时,可以使用 Ctrl+C 或 Ctrl+D 来终止程序的执行,它们的区别如下:

1.  Ctrl+C(SIGINT 信号):该命令会向正在运行的程序发送一个中断信号,使程序立即停止执行,并返回一个错误代码。通常情况下,程序可以捕获该信号并执行一些清理操作,比如关闭文件等。
    
2.  Ctrl+D(EOF 信号):该命令会向正在运行的程序发送一个 EOF(End of File)信号,使程序认为输入结束。这个信号通常用于标准输入流,当程序从标准输入中读取到 EOF 信号时,就会停止等待继续输入,并开始处理输入的数据。
    

因此,Ctrl+C 主要用于终止正在执行的程序,而 Ctrl+D 主要用于向正在等待输入的程序发送一个信号,通常用于与标准输入流相关的程序。

需要注意的是,有些程序可能会忽略 Ctrl+C 或 Ctrl+D 的信号,或者对这些信号进行特殊处理,因此在实际使用中需要根据程序的具体情况来选择使用哪个信号。

【语法】函数

Pasted image 20230417001341.png

  • 方法一实例
#!/bin/bash

printName(){
	if [ $# -lt 2 ]; then
		echo "illegal parameter."
		exit 1
	fi

	echo "firstname is :$1"
	echo "lastname is :$2"
}

printName jackey chen
# 注意不加括号
  • 方法二实例
function test(){
	local word="hello world"
	echo $word
	return 10

	unset word
}
# 一般在函数体内,为了不污染外部变量,使用local控制变量作用范围
# 在函数末尾使用unset取消变量

content=`test`
# 没有参数的函数,可以直接使用如下方式调用
# test
# 就可以了,不加()

echo "status code : $?"

echo "execv result : $content"
  • #注意
    • shell 自上而下执行,函数必须在使用之前定义
    • 函数获取变量与shell脚本类型,0代表函数名,后续参数通过0代表函数名,后续参数通过 1 $2 ……获取
    • 函数内 return 仅仅表示 函数执行状态,不表示函数执行结果
    • 返回结果一般使用 echo printf 在外面使用 $() `` 获取结果
    • 如果没有 return ,函数状态时上一条命令的执行状态,存储在 $?

Pasted image 20230417002245.png

shell 函数-菜鸟

函数语法及返回值

# 语法
[ function ] funname [()]  
  
{  
    action;  
    [return int;]  
}
  • 1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
  • 2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255
  • 总结一下参数的返回值及获取问题
    • 如果有return,就有指定的返回值,如果没有就返回的是最后一条语句的执行结果
    • 使用 result=funcitonname [pars] 得到的返回结果(function 前后有小顿号调用),可以随时调用随时获取,并把返回结果存下来随时用
    • 使用 functionname [pars] 直接调用后,只能在当下立即使用 $? 调用返回结果,有点类似于寄存器,如果不保存结果,后面就会覆盖掉

函数参数

  • 以下是列举的可用的函数参数
$1 …… 表示调用时后面跟的第1 2 个 等等的参数
$# 传递到脚本或函数的参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$! 后台运行的最后一个进程的ID号
$@ 与$*相同,但是使用时加上引号,并在引号中返回每一个参数
$- 显示shell当前使用的选项
$? 显示命令最后的退出状态

shell 传递参数-菜鸟

  • 按照 [[03- shell 脚本和编程#函数参数]]]传参,脚本和函数很像,相同
  • 对应前文中 [[03- shell 脚本和编程#系统环境变量]] 与 [[03- shell 脚本和编程#函数参数]]

【语法】模块化

Pasted image 20230417010346.png

  • 简单来说,就是在一个shell文件中调用另一个shell文件
  • 上述代码左边是 math.sh 文件 ,右边使用 source 命令包含文件后,调用 add 函数,类似于 #include

shell 文件包含-菜鸟

【命令】常用命令

Pasted image 20230417010506.png

shell echo printf test-菜鸟

echo

# 命令执行结果
echo `funciton`
注意使用 ` `

printf

test

  • Shell test 命令 | 菜鸟教程 (runoob.com)
  • 完成 数值测试 文件测试 字符串测试 命令 主要用于判断条件 [[03- shell 脚本和编程#【语法】判断命令]]
  • 链接中给出了该指令在三种情况的详细参数

03 执行过程与原理

执行

  1. shell脚本一般用 .sh 结尾,这是一个约定,可以没有
  2. shell脚本的第一行需要指定使用什么解释器运行
#! /bin/bash
#! /usr/bin/env bash
  1. 启动方式
# 文件名运行
# 需要先加权限
./filename.sh

# 编译器直接运行
# 可以没有权限
bash ./filename.sh


# source 运行
# 可以没有执行权限
source ./filename.sh
  • source与bash执行的区别
这两种方式的区别如下:
1.  使用 `bash` 命令执行脚本时,脚本会在一个新的子进程中执行,这个子进程拥有自己的环境变量和工作目录。脚本中定义的变量和函数也只在子进程中有效,在执行完脚本后不会对当前 Shell 的环境产生任何影响。
2.  使用 `source` 命令执行脚本时,脚本会在当前 Shell 中执行,脚本中定义的变量和函数会在当前 Shell 的环境中生效。这意味着可以在脚本中定义变量和函数,并在脚本执行完后在当前 Shell 中使用它们。
因此,当脚本中定义了一些环境变量或者执行了一些需要在当前 Shell 中生效的操作时,使用 `source` 命令执行脚本会更为方便。
总之,使用 `bash` 命令和使用 `source` 命令执行 Shell 脚本的区别在于脚本在哪个进程中执行,以及脚本中定义的变量和函数的生效范围。需要根据具体情况选择不同的执行方式。

执行过程

  • 这部分更像是编译原理的内容,所以略过一遍

Pasted image 20230417012059.png

  • 如上图
  1. 字符解析
  2. shell展开
  3. 重定向
  4. 执行命令

shell 展开

展开规则

  • 主要包含以下几种展开机制

Pasted image 20230417012132.png

大括号展开

Pasted image 20230417012236.png

波浪号展开 Tilde Expansion

  • 目录与路径的展开

Pasted image 20230417012406.png

参数展开

Pasted image 20230417012345.png

Pasted image 20230417012406.png

Pasted image 20230417012436.png

Pasted image 20230417012542.png

命令替换

  • 子进程 中执行命令,并用结果替换包裹内容
  • 形式
    • $( )
    • 两个反引号

Pasted image 20230417012600.png

数学计算

  • bash中对数值直接计算支持不好
  • 使用 (()) 进行涉及到直接使用数值的计算表达式

Pasted image 20230417012615.png

文件名展开

Pasted image 20230417012640.png

04 调试与前端集成

调试

  1. 使用普通 log 打印,使用 echo,printf
  2. 使用set命令
    1. Pasted image 20230417134205.png
    2. 一般在开头设置
    3. Pasted image 20230417012757.png
  3. VScode debug插件

VScode 配置

  1. 插件推荐
    1. shellman 代码提示与自动补全
    2. shellcheck 代码语法校验
    3. shell-format 代码格式化
    4. Bash Debug 支持单步调试
      1. 安装VS插件
      2. 编写 launch.josn 文件
      3. 升级bashx以上版本
  2. bash debug 使用流程 示例

Pasted image 20230417013010.png

前端集成

  • 在前端开发中统筹使用shell与nodejs
  1. node中使用shell:node中 使用 child_process 和 exec 或者 spawn ,通过这些调用shell命令

Pasted image 20230417013138.png

exec 与 spawn 区别
exec 启动一个子 shell 进执行传入的命今 ,并且将执行结果保存在缓冲区中 ,并且缓冲区是有大小限制的 200KB默认,执行完毕通过回调函数返回
spawn默认不适用shell,而是直接启动子进程执行命令, 且会直接返回一个流对象,支持写入或者读取流数啹, 这个在大数据量交互的场景比较适合
  1. 在shell 脚本中使用 node 命令
    1. 在脚本中调用 js 脚本
    2. Pasted image 20230417013539.png
  2. 通过 zx 库,将js脚本的优势与shell脚本的优势进行融合
    1. shell 完成系统操作:文件IO 内存 磁盘系统状态查询等等
    2. node.js 完成应用层能力,网络IO 计算等
    3. 实例 Pasted image 20230417013703.png

小结

  1. Pasted image 20230417013811.png
  2. 本节内容汇总很乱,shell脚本之前没有学过,学到中间的时候,以为主要内容是打算学习shell,后面发现是最终目的将shell与JS融合到前端开发中
  3. 在 02 部分 复习时只需要看 前面带有 【】的部分,初次学习可以先看有 -菜鸟后缀的部分。

反思

  1. 学习一门技术,应该以工作与实际应用为导向
  2. 很多学习的东西倾向于学院派风格,陈旧且不提供适用于实际工作中的经验。