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

46 阅读5分钟

02 命令和语法

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

自定义变量

#!/bin/bash
# 这是脚本文件的shebang,它告诉系统脚本文件使用哪种解释器来执行。在这个例子中,使用的是bash解释器#变量名=变量值(等号左右不能有空格),定义了两个变量page_size和page_num,并分别给它们赋值为1和2
page size=1
page_num=2
​
#将命令复制给变量,这样可以通过变量名来执行命令。
_ls=ls#将命令结果赋值给变量,可以通过变量名来访问命令结果
file_list=$(ls -a)
​
#默认字符串,不会进行 + 运算。所以total的值为"page_size*page_num"字符串
total=page_size*page_num
​
#声明变量为整型(-i),后续的数值计算中,total会被当作整型来处理
let total=page_size*page_num
declare -i total=page_size*page_num
​
#导出环境变量,使得其在子进程中也可用
export total
declare -x total
  • declare [+/-] 选项 变量

    选项含义
    -给变量设定类型属性
    +取消变量的类型属性
    -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:[新增路径]
  1. $#表示当前脚本或函数的参数个数。在shell脚本中,可以通过$#来获取当前脚本或函数的参数个数,并根据参数个数执行相应的操作。
  2. $*表示当前脚本或函数的所有参数列表。在shell脚本中,可以通过$*来获取当前脚本或函数的所有参数,并根据参数列表执行相应的操作。
  3. $?表示上一个命令的退出状态码。在shell脚本中,可以通过$?来获取上一个命令的退出状态码,并根据状态码执行相应的操作。
  4. $PS1表示shell提示符的格式。在shell中,可以通过修改$PS1来自定义shell提示符的格式,以适应个人习惯或需求。
  5. $HOME表示当前用户的主目录。在shell脚本中,可以通过$HOME来获取当前用户的主目录,并根据主目录执行相应的操作。
  6. $PATH表示系统的可执行文件路径列表。在shell中,可以通过修改$PATH来添加或删除系统的可执行文件路径,以便更方便地执行系统命令或自定义命令。

配置文件加载

2023-99 (5)

运算符和引用

2023-99 (6)

使用示例:

# 在一个终端启动node服务
$ node server.js
Server running at http://127.0.0.1:8888/
# 在另一个终端里访问该服务
$ curl http://127.0.0.1:8888/
Hello World# 但是在第一个终端里按 Ctrl+C 中断后,就无法访问了
# 在一个终端启动node服务,并使用符号 & 让命令在后台运行
$ node server.js &
[1] 15446
# 在另一个终端里访问该服务
$ curl http://127.0.0.1:8888/
Hello World# 现在即使在第一个终端里按 Ctrl+C 也不会中断运行,但是如果将终端关闭,服务依旧会停止
# 如果想要关闭终端但是后台依旧运行,可以搭配 nohub
$ nohub node server.js &

管道

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

  • 语法:cmd1 | cmd2

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

    注意:管道命令仅仅处理 stdout(标准输出),对于 stderr(标准错误输出) 会予以忽略,可以在脚本的前面使用 set -o pipefail 设置 shell 遇到管道错误退出

  • 使用示例:

    #!/bin/bash#该命令将文件platform.access.log的内容通过管道符|传递给grep命令,用于查找文件中包含字符串ERROR的行。这个命令的作用是在platform.access.log文件中查找错误信息
    cat platform.access.log | grep ERROR
    ​
    #该命令使用netstat命令来显示所有的网络连接状态,并通过管道符|将结果传递给grep命令,用于查找所有处于ESTABLISHED状态的连接。最后,通过管道符|将结果传递给wc -l命令,用于统计行数。这个命令的作用是统计当前系统中所有处于ESTABLISHED状态的网络连接数量
    netstat -an | grep ESTABLISHED | wc -l
    ​
    #该命令使用find命令在当前目录中查找所有扩展名为.sh的文件,并通过管道符|将结果传递给xargs命令,将查找到的文件名作为参数传递给ls -l命令,用于显示这些文件的详细信息。这个命令的作用是列出当前目录中所有扩展名为.sh的文件的详细信息
    find . -maxdepth l -name "*.sh" | xargs ls -l
    

重定向

  • Shell 命令在执行的时候会有三个文件描述符 fd0、fd1、fd2,分别表示标准输入、标准输出和标准错误输出。默认这三个文件描述符会指向终端的输入、输出,重定向可以修改这种默认的引用关系。

    2023-99 (7)

  • 比如将 fd0 指向某一个文件,fd1 和 fd2 指向某一个文件,这里就分为了输入重定向和输出重定向。

    2023-99 (8)

    1. >:将命令的输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则覆盖。
    2. >>:将命令的输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则在文件末尾追加。
    3. 2>:将命令的错误输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则覆盖。
    4. &>:将命令的输出和错误输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则覆盖。
    5. <:将一个文件的内容作为命令的输入。
    6. <<:将一段字符串作为命令的输入。

使用示例:

# ls -l 的执行结果保存到 list.txt 文件中,同时将错误输出也保存到这个文件当中
$ ls -l >> list.txt 2>&1
# 这里是一个循环代码。循环的条件是从标准输入读取信息,然后将这个标准输入使用输入重构定向到刚才的 list.txt 文件,这样 read 会从文件当中一行一行读取,然后通过对每一行的信息做一个 cut 操作,取第一列保存到 auth.txt 文件当中
$ while read -r line; do
 echo $line | cut -d " " -f 1 | xargs >> auth.txt
done < ./list.txt
# 执行之后可以看一下 auth.txt 文件
$ cat auth.txt
# 两个小于号 << 这样的输入重定向,它代表继续沿用当前的标准输入,暗示当识别到指令符号的时候,就会停止接受,然后将已经收到的结果传递给命令
$ wc -l <<EOF
heredoc> a
heredoc> b
heredoc> c
heredoc> d
heredoc> EOF
       4
# 上面我们输入a、b、c、d,当输入EOF的时候就会停止输入,同时将已经接受到的四行传递给wc,然后wc统计出来4

以下是其他例子:

  1. ls > file.txt:将ls命令的输出重定向到file.txt文件中,如果文件不存在则创建,如果文件已经存在则覆盖。
  2. ls >> file.txt:将ls命令的输出重定向到file.txt文件中,如果文件不存在则创建,如果文件已经存在则在文件末尾追加。
  3. ls 2> error.txt:将ls命令的错误输出重定向到error.txt文件中,如果文件不存在则创建,如果文件已经存在则覆盖。
  4. ls &> output.txt:将ls命令的输出和错误输出重定向到output.txt文件中,如果文件不存在则创建,如果文件已经存在则覆盖。
  5. sort < file.txt:将file.txt文件的内容作为sort命令的输入。
  6. cat << EOF:将下面的一段字符串作为cat命令的输入直到遇到EOF为止。

判断命令

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

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

语法:

  • test condition
  • [ condition ]
  • [[ condition ]]
#!/bin/bash# 整数测试
test $nl -eq $n2   # 测试 $nl 是否等于 $n2
test $nl -lt $n2   # 测试 $nl 是否小于 $n2
test $nl -gt $n2   # 测试 $nl 是否大于 $n2# 字符串测试
test -z $str_a     # 测试 $str_a 是否为空
test -n $str_a     # 测试 $str_a 是否非空
test $str_a = $str_b   # 测试 $str_a 是否等于 $str_b# 文件测试
test -e /dmt && echo "exist"   # 测试 /dmt 是否存在,并在存在时输出 "exist"
test -f /usr/bin/npm && echo "file exist"   # 测试 /usr/bin/npm 是否存在,并在存在时输出 "file exist"
#!/bin/bash
​
name="hello world"
​
[ $name == "hello" ]
​
# 在判断上面两个变量是否相等的时候会报错
# 这是因为 $name 在命令中会被解析为 [ hello world == "hello" ]
# 所以我们最好使用双引号将变量包裹起来 [ "$name" == "hello" ] 这样就会被正常地解析为 [ "hello world" == "hello" ]
script.sh: line 5: [: too many arguments
​
Exited with error status 2

注意

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

分支语句

在shell中,分支语句主要有if语句和case语句

if 语句

  • 语法:其中 condition 表示判断条件,如果有多个判断条件的话可以使用 elif 表示 elseif,然后是 else 程序段,最后是 fi 结尾。

    if condition ; then
        程序段
    elif condition ; then
        程序段
    else
        程序段
    fi
    
  • 示例:首先判断 level 是否非空字符串,如果非空的话再判断是否等于0或1

    #!/bin/bash
    ​
    level=0
    ​
    if [ -n "$level" ]; then
        if [ "$level" == 0 ]; then
            prefix=ERROR
        elif [ "$level" == 1 ]; then
            prefix=INFO
        else
            echo "log level not supported"
        fi
    fiecho "[${prefix}] $message"
    
  • 示例:下方是一个连续判断的例子,我们可以在方括号内部使用 -o-e 表示 的关系,我们也可以将两个判断拆开然后用或操作符 || 来连接。

    #!/bin/bashread -p "please inpout (Y/N) : " yn
    ​
    if [ "$yn" == "y" -o "$yn" == "Y" ]; then
     echo "ok continue"
    fiif [ "$yn" == "y" || "$yn" == "Y" ]; then
     echo "ok continue too"
    fi
    

case 语句

  • 语法:

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

    #!/bin/bash
    ​
    name=joincase $name in
        "nick")
            echo "hi nick"
        ;;
        "join")
            echo "my name is john"
        ;;
        *)
            echo "404"
        ;;
    esac
    

循环

while 循环

  • 表示条件成立的时候执行循环

  • 语法:while condition ; do 程序段; done

  • 示例:

    #!/bin/bashlet num=0
    ​
    while [ $num -lt 10 ];
    do
        echo "current idx: $num"
        ((num++))
    done
    

until 循环

  • 表示条件成立的时候跳出循环

  • 语法:until condition ; do 程序段; done

  • 示例:

    #!/bin/bashlet num=0
    ​
    until [ $num -gt 10 ];
    do
        echo "current idx: $num"
        ((num++))
    done
    

for 循环

  • 语法:for var in [words...]; do 程序段; done

  • 示例:

    #!/bin/bash# 写法1,对列表内容进行循环
    for foo in a b c;
    do
        echo $foo
    done# 写法2,数值方式循环
    for((i=0;i<10;i++))
    do
        echo $i
    done
    

函数

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

示例:

#!/bin/bashprintName() {
    if [ $# -lt 2 ]; then
        echo "illegal parameter."
        exit 1
    fi
    
    echo "firstname is :$1"
    echo "lastname is: $2"
}
​
# 引用函数,后面跟的是两个参数
printName jacky chen

注意点:

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

示例:

#!/bin/bashfunction test() {  # 定义一个名为test的函数
  local word="hello world"  # 定义一个名为word的本地变量,值为"hello world"
  echo $word  # 输出word的值到标准输出
  return 10  # 返回值为10
  
  unset word  # 没有执行到这里,因为前面已经执行了return语句
}  # 函数定义结束
​
content=`test`  # 执行test函数,并将输出赋值给变量content
echo "状态码:$?"  # 打印上一条命令的状态码,应该是10
echo "执行结果:$content"  # 打印变量content的值,即"hello world"

模块化

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

#!/bin/bash# add 函数
# @return platForm
function add() {
    declare -i res=$1+$2
    echo $res
}
#!/bin/bash
​
sourse './math.sh'
total=$(add 1 2)
echo $total

常用命令

2023-99 (9)

使用案例:

# 对日志文件搜索出错误日志
cat cloudfun.log | grep -n "ERROR"# 对日志文件搜索出错误日志,-t " " 表示按照空格分割,-k 3 代表用分割后的第三列的值去排序
cat cloudfun.log | grep -n "ERROR" | sort -t " " -k 3
​
# 查看错误日志前后的上下文,这里的-A和-B代表 after 和 before
grep "ERROR" cloudfun.log -A3 -B3
​
# head 和 tail 比较类似,代表查看一个文件的前n行/后n行
tail -n 10 cloudfun.log
​
# 标志位 -f 表示读取到文件的末尾之后不会停止命令的执行,会继续等待文件的输入,一般在排查问题的时候经常用到
tail -n 10 -f cloudfun.log