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

177 阅读7分钟

写在前面

学习shell的价值:

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

Shell基础概念

Shell是一种命令行解释器,它是用户与操作系统内核之间的接口。它允许用户输入命令并将其传递给操作系统内核执行。Shell还提供了许多实用程序和工具,例如文件管理、文本编辑、进程管理等。在Unix和Linux系统中,常见的Shell包括Bash、Zsh、Ksh等。Windows系统中的Shell是命令提示符(Command Prompt)和PowerShell。

发展

发展

构成

Shell由命令解释器(解释器)实用程序(编程语言)组成。

命令解释器是Shell的核心组件,它负责解释和执行用户输入的命令。常见的命令解释器包括Bash、Zsh、Ksh等。实用程序是Shell提供的一系列工具和程序,用于完成各种任务,例如文件管理、文本编辑、进程管理等。常见的实用程序包括ls、cd、cp、mv、rm等。

构成

命令和语法

Shell命令是一种在Shell中执行的指令,用于完成各种任务,例如文件管理、文本编辑、进程管理等。Shell命令通常由命令名和一些选项和参数组成。Shell语法是一种用于编写Shell脚本的语言,它由一系列Shell命令和控制结构组成

变量

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

父子shell

自定义变量

#!/bin/bash

# 变量名=变量值
test=123
# 将命令结果赋值给变量
_ls=ls
# 将命令结果赋值给变量
output=$(ls /)
# 声明变量为整型(两种)
let total=size*count
declare -i total=size*count
# 导出环境变量(两种)
export total
declare -x total

declare

注意:

  1. 等号左右不能由空格
  2. 只有total=size*count默认为字符串,不会进行运算。

系统环境变量

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

屏幕截图 2023-04-15 190835.png

配置文件加载

在Shell中,可以使用source命令.命令来加载配置文件。这两个命令的作用是相同的,都是将指定的文件中的命令和变量加载到当前Shell会话中。

source ~./bashrc
. ~./bashrc

配置文件加载

屏幕截图 2023-04-15 191029.png

运算符和引用

类型符号作用用法
算数运算符+ - * / % | &常规运算
逻辑运算符|| && !
比较运算符== != < >
引号双引号 ""部分引用, 仅仅 $ ` \ 保留作用foo="${a}123"
引号单引号 ''完全引用,原样输出foo=‘foo$a’
引号反引号 ``执行命令foo=`ls -a`
圆括号(())算数运算foo=$((1+2))
圆括号()执行命令$(ls -a)
命令连接||cmd1执行完且返回码非0,则继续执行cmd2cmd1 || cmd2
命令连接&&cmd1执行完且返回码为0,则继续执行cmd2cmd1 && cmd2
命令连接cmd1、cmd2串行执行cmd1 ; cmd2
后台运行&让命令在后台运行,可与nohup一起使用cmd &
# 在运行node的时候不影响shell命令(shell关闭也会停止)
node xxxx &  
# shell关闭不停止
nohup node xxxx &

管道

在Shell中,管道符(|)用于将一个命令的输出作为另一个命令的输入。这使得可以将多个命令组合在一起,以便在单个命令行中执行多个操作,作用是将前一个命令的结果传递给后一个命令

  • 语法为:cmd1 | cmd2,
  • 要求:管道右侧的命令必须能接受标准输入才行,比如 grep 命令,ls、mv等不能直接使用,可以使用 xargs预处理
  • 注意:管道命令仅仅处理stdout,对于stderr会予以忽略,可以使用set -o pipefail设置shell遇到管道错误退出
find . -maxdepth 1 -name "*.sh" | xargs ls -l

重定向

在Shell中,重定向是指将命令的输入或输出从默认的位置重定向到其他位置。重定向可以使用特殊字符<>来实现。

重定向

输出重定向符号:

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

输入重定向符号:

  • <:将文件的内容作为命令的输入
  • <<:将文本块作为命令的输入

判断命令

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

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

语法:

  • test condition
  • [ condition ]
  • [[condition]]
# 整数测试
test $n1 -eq $n2
test $n1 -lt $n2
test $n1 -gt $n2
# 字符串测试
test -z $str
test -n $str
# 文件测试
test -e /demo && echo "success"
test -f /usr/bin/npm && echo "success"

注意:

  1. 不能hello world = “hello",会报错,该使用"hello world" = “hello"。
  2. 整数测试中,比较操作符包括-eq(等于)、-ne(不等于)、-lt(小于)、-le(小于等于)、-gt(大于)和-ge(大于等于)。
  3. 字符串测试中,-z选项用于检查字符串长度是否为零。如果字符串长度为零,则返回true;否则返回false。-n选项用于检查字符串长度是否不为零。如果字符串长度不为零,则返回true;否则返回false。
  4. 文件测试中,test -e用于检查/demo是否存在,test -f用于检查文件是否存在并且是一个常规文件。
  5. 中括号前后要有空格符
  6. [和test是命令, 只能使用自己支持的标志位,<、>、=只能用来比较字符串。
  7. 中括号内的变量,最好都是用引号括起来。
  8. [[更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则

分支语句

在Shell脚本中,分支语句用于根据条件执行不同的命令

语法1:

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

语法2:

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

循环语句

在Shell脚本中,循环语句用于重复执行一组命令。循环语句有:

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

函数

在Shell中,可以编写函数封装某一些功能。

语法1:

funcName(){ 
    echo "abc"; 
}

语法2:

function funcName() { 
    echo "abc";
}

注意:

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

模块化

模块化的原理是在当前shell内执行函数,语法为

source [函数库路径]

常用命令

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

执行过程和原理

Shell是一种命令行解释器,它允许用户与操作系统进行交互。当用户在Shell中输入命令时,Shell会解释并执行这些命令。

执行

  1. shell脚本一般以.sh结尾,也可以没有,这是一个约定;第一行需要指定用声明用什么命令解释器来执行。
# 两种方式
#! /bin/bash
#! /usr/bin/env bash
  1. 启动方式
# 文件名运行
./test.sh
# 解释器执行
bash ./test.sh
# source执行
source ./test.sh

执行过程

  1. 字符解析
    • 识别换行符、分号(;)做行的分割
    • 识别命令连接符(|| && 管道) 做命令的分割
    • 识别空格、tab符,做命令和参数的分割
  2. shell 展开,例如 {1..3} 解析为 1 2 3
  3. 重定向,将stdin、stdout、stderr的文件描述符进行指向变更
  4. 执行命令
    • builtin 直接执行
    • 非builtin使用$PATH查找,然后启动子进程执行
  5. 收集状态并返回

执行过程

shell展开

  1. 大括号展开(Brace Expansion){...}
  2. 波浪号展开(Tilde Expansion) ~
  3. 参数展开(Shell Parameter Expansion)
  4. 命令替换(Command Substitution)
  5. 数学计算(Arithmetic Expansion) $((..))
  6. 文件名展开(Filename Expansion)* ? [..] 外壳文件名模式匹配

大括号展开

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

# 字符串序列
a {b c d} e => abe ace ade
# 表达式序列
{1 .. 5} => 1 2 3 4 5
{a .. e} => a b c d e
{1 .. 5 .. 2} => 1 3 5

波浪号展开

# 当前用户主目录
~   # HOME
~/test   # HOME/test
# 指定用户的主目录
~test/foo   # HOME/foo
# 当前工作目录
~+/foo  # /PWD/foo
# 上一级工作目录
~-/foo  # /foo

参数展开

  1. 间接参数扩展${!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

命令替换

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

echo $(ls /)
function fun() {
    echo "hello"
}
a = `fun`

数学计算

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

echo $((1+2))    # 3

文件名展开

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

# 查找所有以f开头的文件或目录
echo f*

调试和前端集成

shell也经常用于前端的调试以及集成工具中。

调试

  1. 普通log,使用echo、printf
  2. 使用set命令
  3. vscode debug插件
set配置作用补充
-u遇到不存在的变量就会报错,并停止执行。-o nounset
-x运行结果之前,先输出执行的那一行命令。-o xtrace
-e只要发生错误,就终止执行-o errexit
-opipefail管道符链接的,只要一个子命令失败,整个管道命令就失败

前端集成

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