【字节前端训练营03】Shell 脚本和编程(3) | 青训营笔记

74 阅读3分钟

03 执行过程和原理

执行

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

    #! /bin/bash
    
    #! /user/bin/env bash
    
  2. 启动 Shell 脚本的三种方式

    # 直接以文件名运行,前提是文件名要有可执行权限(子进程执行)
    ./filename.sh
    ​
    # 解释器跟上文件路径来运行(子进程执行)
    bash ./filename.sh
    ​
    # 使用source运行(当前进程中执行)
    source ./filename.sh
    

执行过程

  1. 字符解析

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

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

  4. 执行命令

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

Shell展开

Shell 展开是指在执行命令之前,Shell 预处理命令行中的各种特殊字符,将它们替换为实际的值或执行相应的操作。

1. 大括号展开

大括号展开(Brace Expansion):使用大括号 {} 将一组字符串包含在命令行中,Shell 将展开大括号内的所有字符串,并将其作为命令行的一部分。例如:echo file{1..3}.txt 将展开为 file1.txt file2.txt file3.txt

一般由三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式 {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
{1..5..2} => 1 3 5
# 字母也可以展开,但是不可以调整增量
{a..e} => a b c d e

2. 波浪号展开

波浪号展开(Tilde Expansion):使用波浪号 ~ 将用户名或路径名包含在命令行中,Shell 将展开波浪号并将其替换为相应的用户名或路径名。例如:cd ~/Documents 将进入当前用户的 Documents 目录。

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

3. 参数展开

参数展开(Parameter Expansion):使用 $ 将变量名包含在命令行中,并使用一些特殊符号来修改变量的值或进行字符串操作。例如:${var:-default} 表示如果变量 $var 未定义,则使用默认值 default

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

    parameter="var"  # 定义一个名为 parameter 的变量,其值为字符串 "var"
    var="hello"  # 定义一个名为 var 的变量,其值为字符串 "hello"
    echo ${!parameter}  # 使用参数展开的形式,将变量 $parameter 的值 "var" 替换为 $var,从而得到变量 $var 的值 "hello",并将其输出到标准输出
    ​
    #输出 hello
    
  2. 参数长度 ${#parameter}

    par=cd
    echo $(#par)
    # 输出2
    
  3. 空参数处理

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

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

    $parameter:?word) # 为空报错

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

    a=1  # 定义一个名为 a 的变量,其值为整数 1
    ​
    echo ${a:-word} #1  
    # 输出变量 $a 的值,如果 $a 未定义或为空,则输出 "word"。由于 $a 已经定义并赋值为 1,因此输出为 1。
    ​
    echo $(b:-word) #word  
    # 执行命令替换,执行命令 $b 并将其输出作为结果,如果 $b 未定义或为空,则输出 "word"。由于 $b 未定义,因此输出为 "word"
    ​
    ​
    echo $(par:=word) #word  
    # 执行命令替换,将变量 $par 的值设置为 "word",并将其输出作为结果。由于 $par 未定义,因此输出为 "word"
    ​
    echo $(par:-hello) #word  
    # 执行命令替换,输出变量 $par 的值,如果 $par 未定义或为空,则输出 "hello"。由于 $par 未定义,因此输出为 "word"
    ​
    ​
    echo $(par:+foo) #foo  
    # 执行命令替换,如果 $par 已经定义且非空,则输出 "foo",否则不输出任何内容。由于 $par 未定义,因此不输出任何内容。#输出分别为:1 word word word foo
    
  4. 参数切片

    ${parameter:offset)

    ${parameter:offset:length}

    # 这个和 JS 数组的 slice 方法比较像,这里就不举例了
    
  5. 参数部分删除,表示对参数进行掐头去尾的操作

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

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

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

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

    #!/bin/sh
    ​
    str=abcdefg  # 定义一个名为 str 的变量,其值为字符串 "abcdefg"
    ​
    spl=$(str##*d)  # 执行命令替换,删除变量 $str 开头到最后一个 "d" 之前的所有字符,并将剩余的字符串赋值给变量 $spl。由于变量 $str 的值为 "abcdefg",因此 $spl 的值为 "efg"。
    # * 是通配符的一种,表示匹配任意数量的任意字符(包括 0 个字符)。具体来说,*d 表示匹配以字母 “d” 结尾的字符串,并将匹配到的字符串删除。而 ## 表示从变量的开头开始匹配,删除最长的匹配项。
    ​
    sp2=${str%%d*}  # 执行参数展开,删除变量 $str 最后一个 "d" 以及之后的所有字符,并将剩余的字符串赋值给变量 $sp2。由于变量 $str 的值为 "abcdefg",因此 $sp2 的值为 "abc"。echo $spl #输出efg  # 输出变量 $spl 的值,即字符串 "efg"。echo $sp2 #输出abc  # 输出变量 $sp2 的值,即字符串 "abc"。# echo的两个输出分别如下:efg abc
    

4. 命令替换

命令替换(Command Substitution):使用反引号或 $() 将一个命令包括起来,Shell 将执行该命令并将其输出作为命令行的一部分。例如:echo "The date is $(date)" 将输出当前日期和时间。

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

#! /bin/bashecho ${whoimi}  # 输出变量 $whoimi 的值。由于 $whoimi 未定义,因此输出为空。foo(){  # 定义一个名为 foo 的函数
echo "asdasd"  # 在函数中输出字符串 "asdasd"
}
​
a=`foo`  # 执行命令替换,执行函数 foo 并将其输出作为结果,将结果赋值给变量 $a。由于函数 foo 的输出为 "asdasd",因此变量 $a 的值为 "asdasd"。

5. 数学计算

数学计算(Arithmetic Expansion):使用 $(( )) 将一个算术表达式包含在命令行中,Shell 将计算该表达式并将其替换为计算结果。例如:echo $((1+2)) 将输出 3

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

#! /bin/bashecho $((1+2)) # 3

6. 文件名展开

文件名展开(Filename Expansion):使用通配符 *?[] 等来匹配文件名或路径名。Shell 将展开通配符并将其替换为匹配的文件名或路径名。例如:ls *.txt 将列出当前目录下所有扩展名为 .txt 的文件。

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

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