Shell脚本的执行过程和原理 | 青训营笔记

169 阅读2分钟

Shell脚本的执行过程和原理

Shell脚本一般以.sh结尾,也可以没有,这是一个约定

在脚本第一行需要指定使用什么命令解释器来执行:

#! /bin/bash

or

#! /usr/bin/env bash

启动shell脚本的方式:

  1. 文件名运行 :./filename.sh
  2. 解释器运行 :bash ./filename.sh
  3. source运行:source ./filename.sh

执行过程

  1. 字符解析

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

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

  4. 执行命令

    1. builtin直接执行
    2. 非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
​
#表达式序列, (数字可以使用incr调整增量,字母不行)
{1..5} => 1 2 3 4 5
{1..5..2} => 1 3 5
{a..e} => a b c d e
波浪号展开
# 当前用户主目录
- => $HOME
-/foo => $HOME/FOO
​
# 指定用户的主目录
-fred/foo => 用户fred的 $HOME/foo
​
# 当前工作目录
-+/foo => $PWD/foo
​
# 上一个工作目录
--/foo => $ ${SOLDPWD-'--'}/foo
参数展开
  1. 间接参数扩展 ${!parameter},其中引用的参数并不是parameter而是parameter的实际的值

    parameter="var"
    var="hello"
    echo ${!parameter}
    # 输出hello
    

    在echo输出parameter时,会逐层展开,先去寻找parameter,发现其对应var,然后再去寻找var,最终找到hello

  2. 参数长度${#parameter}

    par=cd
    echo ${#par}
    # 输出2
    
  3. 空参数处理 {parameter:-word} # 为空替换 {parameter:=word} # 为空替换,并将值赋值给parameter变量parameter变量 {parameter:?word} # 为空报错 ${parameter:+word} # 不为空替换

    a=1
    ​
    echo ${a:-word} # 因为a有值,所以输出1
    echo ${b:-word} # 因为b为空,所以输出wordecho ${par:=word} # 因为par为空,所以会输出word,且赋值给par
    echo ${par:-hello} # 因为par已经为word,所以输出wordecho ${par:+foo} # 因为par不为空,所以不为空替换输出foo
    
  4. 参数切片 parameter:offset{parameter: offset} {parameter: offset: length}

  5. 参数部分删除 {parameter%word} # 最小限度从后面截取word {parameter%%word} # 最大限度从后面截取word ${parameter##word} # 最大限度从前面截取word

    str=abcdefg
    # ##表示最大程度从前方匹配截取,配合*匹配abcd
    sp1=${str##*d}
    # %%表示最大程度从后方匹配截取,配合*匹配defg
    sp2=${str%%d*}
    ​
    echo $sp1 # 输出efg
    ​
    echo $sp2 # 输出abc
    
命令替换

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

$(...)或`...`
echo ${whoimi}foo(){
    echo "asdasd"
}
​
a=`foo`
数学计算

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

echo $((1+2)) # 3
文件名展开

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

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