Shell脚本和编程 | 青训营笔记

170 阅读10分钟

一、Shell基础概念

概念

终端获取用户输入,展示运算结果的硬件设备。 台式机是由主机(机箱)和很多外围设备组成的。主机里边包括主板、CPU、内存、硬盘、和其他芯片等等。外围设备包括显示器、鼠标、键盘、耳机、麦克风、和摄像头等等。这些外围设备就被称为终端,负责向主机输入数据的就叫输入终端,比如鼠标、键盘、麦克风、摄像头,负责接收主机输出数据的设备就被称作输出终端,比如显示器、耳机。

终端 = 输入终端 + 输出终端

tty:teletypeWriter 的简称,和终端等价,早期指电报打印机,在 linux 中是输入输出环境。 tty或者说终端最开始指的是获取用户输入并输出的物理设备,比如电传打字机 在linux.中是接收用户输入、输出结果的终端仿真软件,比如我们用的mac terminal、iterm2等,更强输入辅助功能、画面绘制输出的模拟终端器;而tty变成一个虚拟概念,是linux的一个程序,每个终端模拟器关联一个虚拟tty,和内核打交道。我们可以在终端模拟器中输入tty查看关联到的虚拟ttybash是shell的一种具体实现,可以理解成实例和类的关系

终端模拟器:Mac Terminal、iTerm2 等,关联虚拟 tty 的输入输出软件。终端成为内核的一个模块,它可以直接向 TTY 驱动发送字符,并从 TTY 驱动读取响应然后打印到屏幕上。也就是说,用内核模块模拟物理终端设备,因此被称为终端模拟器。

Shell:command interpreter,处理来自终端模拟器的输入,解释执行之后输出结果给终端。Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。

Bash:Shell 的一种具体实现。它被设计用来听从用户的命令,做用户让它做的事。

image.png 上图是一个典型的 Linux 桌面系统。终端模拟器就像过去的物理终端一样,它监听来自键盘的事件将其发送到 TTY 驱动,并从 TTY 驱动读取响应,通过显卡驱动将结果渲染到显示器上。

发展

1、Ken Thompson(来自贝尔实验室)在 1971 年为 UNIX 开发了第一个 shell,称为 V6 shell。

2、Stephen Bourne 在贝尔实验室 为 V7 UNIX 所开发的 Bourne shell,即 sh。

3、开源组织 GNU 为了取代 Bourne shell 开发的 Bourne-Again shell,即 Bash。

image.png

  • 除了替代 v6 shell,sh 还有几个优点,把控制流程,循环,变量引入了脚本,提供了一种更具功能性的语言

  • 主流 Linux 系统使用的 shell,许多都以它为锚点。

  • bash是 sh 的超集,可以直接执行大部分 sh 脚本。 Bash 在兼容 Bourne shell 脚本编程的同时,集成了 Korn shell 和 C shell 的功能,包括命令历史,命令行编辑,目录堆栈(pushd 和 popd),一些实用环境变量,命令自动补全等。

构成

image.png

1、shell 不仅提供了与内核和设备交互的方法,还集成了一些今天软件开发中通用的设计模式(比如管道和过滤器),具备控制流程,循环,变量,命令查找的机制。

2、既是命令解释器,也是一门编程语言,作为命令解释器,它提供给用户接口,使用丰富的 GNU 工具集,第三方的或者内置的,比如 cd、pwd、exec、test、netstat 等。

二、语法和命令

变量

image.png

  • 自定义变量中默认声明的是字符串型,其余需要手动声明。

父子Shell

image.png

  • 自定义变量在父进程中使用,环境变量和系统环境变量在父进程和子进程中都可以使用到。

自定义变量

image.png

  1. !/bin/bash 第一行的内容指定了shell脚本解释器的路径,而且这个指定路径只能放在文件的第一行。第一行写错或者不写时,系统会有一个默认的解释器进行解释。
  2. 使用=赋值时,=左右不能有空格,有空格代表其他命令。
  3. 将命令复制给变量,这样可以通过变量名来执行命令。
  4. 将命令结果赋值给变量,可以通过变量名来访问命令结果。
  5. 声明整形或者进行数学运算,需要用到let,或declare -i来进行声明。

declare声明变量的含义

image.png

系统环境变量

image.png

  1. $n:当前脚本的第n个参数,n=1,2,…,9。
  2. $?:命令或程序执行完后的状态,返回0表示执行成功。
  3. \h:显示简写的主机名。
    @:显示 12 小时制时间,格式为"HH:MM am/pm"。
    \u:显示当前用户名。
    \w:显示当前所在目录的完整名称。

配置文件加载

image.png

1、login shell,登录式的 Shell,首先会读取和执行 /etc/profiles,这是所有用户的全局配置文件,接着会到用户主目录中寻找 /.bash_profile,或者/.bash_login 或者 ~/.profile,它们都是用户个人的配置文件。

  • 如果三个文件同时存在的话。它们的优先级顺序是 ~/.bash_profile > ~/.bash_login > ~/.profile。
  • 如果 ~/.bash_profile 存在,那么一切以该文件为准,并且到此结束,不再加载其它的配置文件。
  • 如果 ~/.bash_profile 不存在,那么尝试加载 ~/.bash_login。
  • 如果~/.bash_login 存在的话就到此结束,不存在的话就加载 ~/.profile。

2、non—login shell,非登录时的shell,直接读取 ~/.bashrc。

3、 ~/.bashrc 文件还会嵌套加载 /etc/bashrc

  • ~/.bashrc是针对整个系统所有用户的,/etc/bashrc是针对特定用户的./etc/bashrc修改了以后要重启系统才生效,而用户目录下.bashrc修改了以后重新登录就生效。

4、修改某配置文件,需要它在shell中生效时,使用source filename执行这个脚本。

运算符和引用

image.png

管道

image.png

如果需要互通,比如第一个命令的返回传递给第二个命令,就需要用到管道了,管道的本质就是将多个程序进行了一个连接,和信号一样,也是进程通信的方式之一

1、第一个命令将文件platform.access.log的内容传递给grep命令,用于查找文件中包含字符串ERROR的行。这个命令的作用是查找platform.access.log文件中的错误信息。

2、第二个命令使用netstat命令来显示所有的网络连接状态,然后将结果传递给grep命令,用于查找所有处于ESTABLISHED状态的连接。最后,将结果传递给wc -l命令,用于统计行数。这个命令的作用是统计当前系统中所有处于ESTABLISHED状态的网络连接数量。

3、第三个命令使用find命令在当前目录中查找所有扩展名为.sh的文件,然后将结果传递给xargs命令,将查找到的文件名作为参数传递给ls -l命令,用于显示这些文件的详细信息。这个命令的作用是列出当前目录中所有扩展名为.sh的文件的详细信息。

重定向

image.png

每个shell命令在执行时都会打开三个文件描述符,文件描述符0、1、2,分别对应itdin,stout,ster,这三个文件描述符默认默认指向终端输入、终端翰H出,那么当命令需要获取输入的时候,它会去读取fdO,当要输出的时候它会像fd1、fd2写入,改变这些描述符指向的行为叫做重定向。

例子:

#将hello world写入users文件
$ echo "hello world" > users
$ cat users
hello world
#追加
$ echo "hello world" >> users
$ cat users
hello world
hello world
#wc -l统计users文件的行数
$  wc -l < users
       2 

判断命令

shell中提供了test、[、[[三种判断符号,可用于:

  • 整数测试
  • 字符串测试
  • 文件测试

语法:

  • test condition
  • [ condition ]
  • [[ condition ]]

注意

  • 中括号前后要有空格符
  • [ 和test是命令,只能使用自己支持的标志位,<、>、=只能用来比较字符串
  • 中括号内的变量,最好都是用引号括起来
  • [[更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则

image.png

shell中的比较运算符:

-eq       //等于
-ne       //不等于
-gt        //大于 (greater)
-lt         //小于 (less)
-ge       //大于等于
-le        //小于等于

shell中的逻辑运算符:

逻辑与:    &&
第一个条件为假时,第二个条件不用再判断,最终结果已经有;
第一个条件为真时,第二个条件必须得判断。
逻辑或:    ||
逻辑非:       !

注意点:

  1. shell中如果是等于、不等于,既可以用 -eq、-ne (外面需要加中括号),也可以用 == 、!=(外面加中括号或双括号都行)
  2. shell中如果是大于,大于等于,小于,小于等于,用 -gt, -ge,-lt,-le 的话,则需要加中括号。
  3. shell中大于、大于等于,小于,小于等于想用 >,>=,<,<=,则需要加双括号,而不是中括号

分支语句

1、if语句

if语句就是shell脚本中实现分支的命令,if脚本有单分支、双分支和多分支三种形式。

if单分支:

if 表达式; then
     执行语句
fi

if双分支:

if 表达式; then
     执行语句
else 
     执行语句
fi

if多分支:

if 表达式; then
     执行语句
elif 表达式;then 
     执行语句
elif 表达式;then 
     执行语句
…………
else 
     执行语句
fi

案例: 输入一个文件,判断是否存在

#!/bin/bash

read -p "Please input a file:" file

if [ -f $file ]; then
    echo "File: $file exists!"
else
    echo "File: $file not exists!"
fi

if表达式中常见的判断运算符如下:
-f 判断文件是否存在
-d 判断目录是否存在
-z 判断是否是空字符串

2、cause语句

case语句主要适用情况

  • 某个变量存在多种取值
  • 需要对其中的每一种取值分别执行不同命令序列

case 分支语句的语法结构:

case 变量值 in
模式 1)
	命令序列1
	;;
模式 2)
	命令序列2
	;;
	.......
*)
	默认命令序列
esac

案例:检查用户输入字符类型

read -p "请输入一个字符,并按Enter键确定:" KEF
case "$KEY" in
 [a-z]|[A-Z])									
 	echo "您输入的是字母!"
 	;;
 [0-9])											
 	echo "您输入的是数字!"
 	;;
 *)												
 	echo "您输入的是空格,功能键或其他控制字符。"
 esac

循环

while循环

语法:

while 表达式
  do
  command
  done

案例:

#!/bin/bash

a=0
while [ $a -le 10 ]
do
  echo $a
   let a++
done

until循环

语法:

until 条件测试操作
do
  命令序列
done

案例:

#!/bin/bash
i=0
sum=0
until [ $i -gt 100]
do
  sum=$(($sum+$i))
  let i++
done
  echo "1-100的和为:$sum"

for循环

语法:

for 变量名 in 取值列表        
do 
   命令序列(命令行)
done

案例:

#!/bin/bash
#for 1+...+100
a=0
for ((i=1;i<=100;i++))
do
  a=$(($a+$i))
done
 echo "1-100所有数字相加的和为$a"

函数

image.png

案例:

#!/bin/bash
# 函数定义
function show(){
   echo "Hello world"
}
# 函数调用
show

运行结果:

Hello world

注意点:

  • shell自上而下执行,函数必须在使用前定义
  • 函数获取变量和 shell script类似,0代表函数名,后续参数通过0代表函数名,后续参数通过1、$2 ...获取函数内 return仅仅表示函数执行状态,不代表函数执行结果
  • 返回结果一般使用echo、 printf,在外面使用$() 、`` 获取结果
  • 如果没有return ,函数状态是上一条命令的执行状态,存储在$?中

模块化

模块化的原理是在当前shell内执行函数文件,方式:source[函数库的路径]

image.png

常用命令

image.png

三、执行过程和原理

  1. shell脚本一般以.sh结尾,也可以没有,这是一个约定;第一行需要指定用什么命令解释器来执行

image.png

2、启动方式

image.png

执行过程

image.png

1、字符解析

  • 识别换行符、分号(;)做行的分割
  • 识别命令连接符(||&&管道)做命令的分割
  • 识别空格、tab符,做命令和参数的分割

2、shell展开,例如{1...3}解析为1 2 3

3、重定向,将stdin、stdout、stderr的文件描述符进行指向变更

4、执行命令

  • builtin直接执行
  • 非builtin使用$PATH查找,然后启动子进程执行

5、收集状态并返回给脚本

shell展开

1、大括号展开{...}

  • 一般由三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式{x..y[..incr]}

image.png

2、波浪号展开~

image.png

3、参数展开${}

  1. 间接参数扩展${!parameter},其中引用的参数并不是parameter而是parameter的实际的值

image.png

  1. 参数长度${#parameter}

image.png

  1. 空参数处理

    ${parameter:-word}#为空替换

    ${parameter:=word}#为空替换,并将值赋给parameter变量

    ${parameter:?word}#为空报错

    ${parameter:+word}#不为空替换

image.png

  1. 参数切片

    ${parameter:offset}

    ${parameter:offset:length}

  2. 参数部分删除

    ${parameter%word} #最小限度从后面截取word

    ${parameter%%word}#最大限度从后面截取word

    ${parameter#word}#最小限度从前面截取word

    ${parameter##word}#最大限度从前面截取word

image.png

4、命令替换

  • 在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种:$(...)或...

image.png

5、数学计算$((..))

  • 使用$( ( ) ) 包裹数学运算表达式,得到结果并替换

image.png

6、文件名展开*?[..]外壳文件名模式匹配

  • 当有单词没有被引号包裹,且其中出现了‘*’,‘?’,and ‘[’ 字符,则shell会去按照正则匹配的方式查找文件名进行替换,如果没找到则保持不变。

image.png

元字符含义
*匹配零或多个字符
?匹配一个字符
[abc]匹配一个字符集内的一个字符,如a,b,c
{a,ile,ax}匹配一个字符或字符集
[!a-z]匹配从a~z范围以外的一个字符
|转义或禁止元字符
{1...10}可以通过范围操作符来进行批量匹配

四、调试和前端集成

调试

  1. 普通log,使用echo、printf
  2. 使用set命令
  3. vscode debug插件

image.png

image.png

VSCode配置

  1. shellman:代码提示和自动补全

  2. shellcheck:代码语法校验

  3. shell-format:代码格式化

  4. Bash Debug:支持单步调试

    • 安装vscode插件
    • 编写launch.json文件
    • 升级bash到4.x以上版本

image.png

前端集成

  1. node中通过exec、spawn调用shell命令
  • exec启动一个子shll进程执行传入的命令,并且将执行结果保存在缓冲区中,并且缓冲区是有大小限制的,执行完毕通过回调函数返回
  • spawn默认不使用shell,而是直接启动子进程执行命令,且会直接返回一个流对象,支持写入或者读取流数据,这个在大数据量交互的场景比较适合

image.png

  1. shell脚本中调用node命令

image.png

  1. 借助zx等库进行javascript、shell script的融合

    • 借助shell完成系统操作,文件io、内存、磁盘系统状态查看
    • 借助nodejs完成应用层能力,网络io、计算等

image.png