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

80 阅读6分钟

Shell脚本与编程

学习Shell的价值

1.Linux服务器的基本操作和管理 2.前端Node.js服务的进程管理、问题排查、资源监控等运维操作 3.使用shel1编写TCE、SCM、Docker脚本,完成服务编译和部署

Shell基本概念

概念

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

终端模拟器:Mac Terminal、iTerm2等.关联虚拟tty的输入输出软件

tty:teletypeWriter的简称,和终端等价.早期指电传印机,在linux中是输入/输出环境

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

Bash:shell的一种具体实现

发展

Ken Thompson(来自贝尔实验室)在1971年为UNX开发了第一个shell,称为V6 shell Stephen Bourne在贝尔实验室为V7UNX所开发的Bourne shell,即sh 开源组织GNU为了取代Bourne shell开发的Bourne-Again shell,Bash

image-20230417092605207

构成

image-20230417092653802

命令语法

类型作用域声明方法规范
自定义变量当前shell=字符串、整型、浮点型、日期型
环境变量当前shell及其子shellexport、declare -x
系统环境变量所有shell启动加载

image-20230417092954773

自定义变量

#!/bin/bash#变量名=变量值(等号左右不能有空格)
page size=1
page_num=2
​
#将命令复制给变量
_1s=ls#将命令结果赋值给变量
file_list=$(1s -a)
​
#默认字符串,不会进行+运算
total=page size*page_num  ×
​
#声明变量为整型
let total=page_size*page_num
declare -i total=page_size*page_num
​
#导出环境变量
export total
​
declare -x total
选项含义
-给变量设定类型属性
+取消变量的类型属性
-a将变量声明为数组类型
-i将变量声明为整数类型
-x将变量声明为环境变量
-r将变量声明为只读变量
-p显示指定变量的被声明的类型

系统环境变量

变量名含义常见操作
$0当前shell名称/脚本名称11、2等可以获取到传入参数
$#传入脚本的参数数量if[$# -gt 1]
$*传入脚本的所有参数
$?上一条命令执行的状态码if [$? -eq 0]
$PS1命令提示符export PS1="\u@\h \w>"
$HOME用户主文件夹cd ~
$PATH全局命令的搜索路径PATH=$PATH:[新增路径]

配置文件加载

image-20230417094348502

运算符和引用

image-20230417162312785

管道

管道与管道符 | ,作用是将前一个命令的结果传递给后面的命令

语法: cmd1 | cmd2

要求:管道右侧的命令必须能接受标准输入才行,比如grep命令,ls、mv等不嗯给你直接使用,可以使用xargs预处理

注意:管道命令仅仅处理stdout,对于stderr会予以忽略,可以使用 set - o pipefail 设置shell遇到管道错误时依然退出

#!/bin/bash
cat platform.access.log | grep ERROR
netstat -an | grep ESTABLISHED | wc -1
find -maxdepth 1 -name "*.sh" | xargs 1s -1

重定向

image-20230417163226106

image-20230417163244896

输出重定向符号

>:覆盖写入文件
>:追加写入文件
2>:错误输出写入文件
&>:正确和错误输出统一写入到文件中

输入重定向符号

<
<<

判断命令

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

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

语法

  • test condition
  • [condition]
  • [[condition ]]
#!/bin/bash#整数测试
test $nl -eq Sn2
test $nl -lt Sn2
test $nl -gt Sn2
#字符串测试
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是命令,只能使用自己支持的标志位,<、>、=只能用来比较字符串

  • 中括号内的变量,最好都是用引号括起来

  • [[更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则

    image-20230417164049220

分支语句

语法1:

if condition then
    程序段
elif condition then
    程序段
esle
    程序段
fi

语法2:

case$变量in:
    "第一个变量内容")
    程序段
    ;;
“第一个变量内容)
    程序段
    ;;
    *)
    程序段
    ;;
esac

循环

#while循环
while condition;do程序段;done
#until循环
until condition;do程序段;done
#for循环
for var in[words...];do程序段;done

函数

语法一:
funcName(){echo "abc";}
语法二:
function funcName(){echo "abc";}

注意:

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

模块化

模块化的原理是当前shell内执行函数文件,方式:

source [函数库的路径]

image-20230417164815727

命令使用
grep查找错误日志:grep -n "ERROR" -A3 -B3 cloudfun.log 统计次数:grep -n "ERROR" -c cloudfun.log
sort指定分隔符后以第三列进行排序:sort -t " " -k 3
wct统计出现的行数、单词数、字符数 wc -lwm
head查看前十行:head -n 10 cloudfun.log
tail等待追加内容:tail -f -n 1- cloudfun.log
cut对数据行的内容进行处理:cut -d " " -f 3
find文件和目录查找
xargs参数处理
which查找命令路径

执行过程和原理

执行

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

2、启动方式

#文件名运行
./filename.sh

#解释器运行
bash ./filename.sh

#source运行
source ./filename.sh

执行过程

image-20230417223752985

1.字符解析

  • 识别换行符、分号(;)做行的分割
  • 识别命令连接符( ||&&管道)做命令的分割
  • 识别空格、tab符,做命令和参数的分割
  1. shell 展开,例如{1..3}解析为1 2 3

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

  3. 执行命令

    • builtin直接执行
    • 非builtin 使用$PATH查找,然后启动子进程执行
  4. 收集状态并返回

shell展开

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

#字符串序列
a{b,c,d}e => abe ace ade
#表达式序列,(数字可以使用incr调整增量,字母不行)
{1..5}=>12345
{1..5..2}>135
(a.,e}=>a b c d e

波浪号展开

(Tilde Expansion)

#当前用户主目录
~ =>$HOME
-/foo=>$HOME/foo
#指定用户的主目录
~fred/foo=>用户fred的$HOEM/foo
#当前工作目录
~+/foo>$PWD/foo
#上一个工作目录
~-/foo ${$OLDPWD-'~-'}/foo

参数展开

(Shell Parameter Expansion) ${}

l.间接参数扩展${parameter},其中引用的参数并不是parameter而是parameter的实际的值
2.参数长度${#parameter}
3.空参数处理
${parameter:-word}#为空替换
${parameter:=word#为空替换,并将值赋给$parameter变量
${parameter:?word#为空报错
${parameter:+word}#不为空替换
4.参数切片
${parameter:offset}
${parameter:offset:length}
5.参数部分删除
${parameter%word}#最小限度从后面截取word
${parameter%%word}#最大限度从后面截取word
${parameter#word}#最小限度从前面截取word
${parameter##word}#最大限度从前面截取word

命令替换

(Command Substitution)

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

echo $(whoimi)
foo(){
	echo "asdasd"
}
a=`foo`

数学计算

(Arithmetic Expansion) $((..))

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

echo $((1+2)) #3

文件名展开

(Filename Expansion) * ?[...]外壳文件名模式匹配

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

$echo D*
#输出当前目录下所有以D字母开头的目录、文件

调试和前端集成

调试

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

set配置作用补充
-u遇到不存在的变量就会报错,并停止执行-o nounset
-x运行结果之前,先输出执行的那一行命令-o xtrace
-e只要发生错误,就终止执行-o errexit
-o pipefail管道符链接的,只要一个子命令失败,整个管道符就失败,脚本就会终止执行

VSCode配置

1.shellman:代码提示和自动补全 2.shellcheck:代码语法校验 3.shell-format:代码格式化 4.Bash Debug:支持单步调试

  • 安装vscode插件
  • 编写launch.json文件
  • 升级bash到4.x以上版本
#1.安装最新版本bash
	brew install bash
	
#2.查看安装路径
	which -a bash
	
#3.将新版本bash路径加入PATH
PATH="/usr/local/bin/bash:$PATH"

#4.配置vscode launch.json启动文件
   { "version":"0.2.0",
    "configurations":[
    {
    "type":"bashdb",
    "request":"launch",
    "name":"Bash-Debug (simplest configuration)",
    "cwd":"S{workspaceFolder}",
    "program":"debug.sh"
    }
  ]
}

前端集成

1.node中通过exec、spawn调用shell命令

spawn

child_process.spawn会返回一个带有stdout和stderr流的对象。你可以通过stdout流来读取子进程返回给Node.js的数据。 stdout拥有’data’,’end’以及一般流所具有的事件。当你想要子进程返回大量数据给Node时,比如说图像处理,读取二进制数 据等等,你最好使用spawn方法 child_process.spawn方法是“异步中的异步”,意思是在子进程开始执行时,它就开始从一个流总将数据从子进程返回给Node

exec

child_process.exec方法是“同步中的异步”,意思是尽管exec是异步的,它一定要等到子进程运行结束以后然后一次性返回所有的buffer数据。 如果exec的buffer体积设置的不够大,它将会以一个“maxBuffer exceeded”错误失败告终

child_process.exec方法会从子进程中返回一个完整的buffer。默认情况下,这个buffer的大小应该是200k。 如果子进程返回的数据大小超过了200k,程序将会崩溃,同时显示错误信息“Error:maxBuffer exceeded”。 你可以通过在exec的可选项中设置一个更大的buffer体积来解决这个问题,但是你不应该这样做,因为exec本来就不是用来返回很多数据的方法。 对于有很多数据返回的情况,你应该使用上面的spawn方法。那么exec究竟是用来做什么的呢?我们可以使用它来运行程序然后返回结果的状态,而不是结果的数据

2.shell脚本中调用node命令 3.借助zx等库进行javascript,shell script的融合 借助shell完成系统操作,文件io、内存、磁盘系统状态查询等 借助nodejs完成应用层能力,网络io、计算等

image2