• Shell脚本简明教程


    1. 简介

    Shell脚本是一种简单的脚本语言,运行在Unix-like的操作系统上,像Linux,mac, unix等。Shell脚本的解析器是shell,Unix-like系统很多,所以出现了很多不同的shell,像tcsh, csh, ash, bash, dash等。
    像Ubuntu默认使用的Shell是dash,其特点是解析执行速度快,缺点是支持的语法特性少。如果要查看自己系统上使用的是何种解释器,可以使用如下命令:

    ls /bin/sh -al
    # or
    echo $SHELL
    
    • 1
    • 2
    • 3

    bash使用更为便捷,此文主要以bash解释器为基础来进行讲解。Shell有一些老的语法形式,不推荐使用,此文不讲解。
    因为系统内置了shell解释器,所以可以直接在控制台窗口上输入Shell脚本来解释执行。也可以将Shell脚本编写到以.sh为后缀的脚本文件当中。然后通过sh/bash调用执行,也可以将脚本文件修改为可执行模式,直接运行。

    2. 语法

    2.1. 注明解释器

    因为Shell脚本的解释器有很多版本,不同版本之间的功能略有差异,为了防止编写的脚本应用时使用的解释器不同而导致脚本功能异常,系统在调用解释器时会先检查脚本文本的第一行,如果有指定解释器路径,则以此解释器来执行当前脚本。

    #!/bin/bash
    
    • 1

    2.2. 注释

    在代码中添加注释,可以提高代码的可读性,降低开发维护难度。

    • 单行注释
    # 此行是注释
    echo start
    
    • 1
    • 2
    • 多行注释
      多行注释可以利用单行注释重复多次来完成,也可以使用特殊形式
    # 此行是注释
    # 此行是注释
    # 此行是注释
    :<<!
    Copyright 2022, Ys Co. Ltd.
    All rights erserved.
    Revision: 1.0.0
    !
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.3. 变量

    • 变量定义
      语法形式:variable_name=“content”
      需要注意,=两边都不能有空格,变量内容可以不加双引号,也可以加双引号(可以防止有空格将内容截断)。
      变量名须满足以下要求:
    • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
    • 中间不能有空格,可以使用下划线 _。
    • 不能使用标点符号。
    • 不能使用bash里的关键字(可用help命令查看保留关键字)。
      Shell脚本的变量,默认都是字符串类型,只是在某些需要整形的语法形式中,编译器会将字符串转换为对应的整形来进行解析,此时如果字符串中有非数字字符,解释器会报错”value too great for base“。Shell中的数字只支持10进制整形。
    • 变量修饰
      – 局部变量,只能用在函数中,且局部变量的定义要与赋值分开。local var
      – 只读变量,防止他人修改的变量,readonly MAX_SIZE=1024
      – 环境变量,用来控制程序启动时的参数。
    • 变量使用
      语法形式:
    $variable_name 
    ${variable_name}$variable_name“
    ”${variable_name}
    • 1
    • 2
    • 3
    • 4

    加双引号是为了防止引用变量中有空格时,作为形参传递给第三方应用程序时被截断。所以推荐第4种用法,但是一些常见的$1,$2等也遵循惯例。关于语法形式,推荐使用shellcheck来校验。

    • 变量删除
      当不想使用变量时,可以删除变量
    unset variable_name
    
    • 1

    2.4. 字符串

    在非数字操作的场景中,所有变量都被当作字符串,字符串可以加单引号,也可以加双引号,也可以不加引号。

    • 字符串长度
    string="abcd"
    echo ${#string}   # 输出 4
    
    • 1
    • 2
    • 切割子字符串
      字符串切片
    str="abcdefg"
    # 从第1个到第4个(从0开始)
    echo ${str:1:4}
    # 从第1个到倒数第2个(从0开始)
    echo ${str:1:-2}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 替换字符串
    ip="172.16.36.40"
    # 只替换第1个.:172-16.36.40
    echo ${ip/./-}
    # 替换所有:172-16-36-40
    echo ${ip//./-}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 删除字符串
    [root@payqa1 work]# echo ${ip}
    172.16.36.40
    # 从头开始删除到第1个.
    [root@payqa1 work]# echo ${ip#*.}
    16.36.40
    # 从头删除到最后1个.
    [root@payqa1 work]# echo ${ip##*.}
    40
    # 从尾部开始删除
    [root@payqa1 work]# echo ${ip%.*}
    172.16.36
    [root@payqa1 work]# echo ${ip%%.*}
    172
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 转义字符
      如果字符串中有转义字符,必须使用双引号。
    name="new line\n"
    echo -e ${name}
    
    • 1
    • 2

    2.5. 数组

    • 数组定义
      Shell脚本只支持1维数组。数组以中括号加空格分割构成,其语法形式:
      array_name=(val1 val2 … valn)
    • 数组访问
      数组以中括号加下标访问
    # 访问第0个元素
    echo $array_name[0]
    # 访问所有元素
    echo ${array_name[@]}
    
    • 1
    • 2
    • 3
    • 4
    • 数组大小
    # 取得数组元素的个数
    length=${#array_name[@]}
    # 取得数组单个元素的长度
    lengthn=${#array_name[n]}
    
    • 1
    • 2
    • 3
    • 4
    • 关联数组
      关联数组,也即字典,通过key和value进行映射。
    declare -A high=(["mike"]=178 ["will"]=190 ["jack"]=172)
    high["lily"]=165
    echo "${high[@]}"
    echo "${high["mike"]}"
    
    • 1
    • 2
    • 3
    • 4
    • 删除数组元素
    arr=(1 2 3)
    unset arr[1]
    echo ${arr[@]}
    
    • 1
    • 2
    • 3

    2.6. 参数

    • 参数调用
      Shell脚本的参数传递方法都是一样的,无论是函数参数、命令行参数还是脚本参数,都是以空格加参数内容构成,如果有多个参数,则多个空格多个参数。
    echo "param1" "param2"
    echo $p1 $2
    ./run.sh "${p1}" "${p2}"
    
    • 1
    • 2
    • 3
    • 参数获取
      如在run.sh脚本中获取调用者传入的参数:
    # $1是脚本名字
    echo "param1:" $1
    echo "param2:" $2
    echo "param count:" $#
    echo "All param:" $@
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.7. 算术运算

    Shell脚本只支持整形计算,包括负数,其范围为64bit大小。
    Shell脚本支持加、减、乘、除和取余操作,其语法主要有两形式。

    let a=4*4
    let a=a*8
    b=8
    # 推荐
    (( b=b*8 ))
    echo $a
    echo $b
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.8. 逻辑判断

    逻辑判断语法形式:

    if expr; then
    # do something
    elif expr
    # do something
    else
    # do something
    fi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    逻辑表达式,针对不同的情况,有不同的表达式

    2.8.1. 数字比较

    a=10
    b=11
    
    if [ $a -eq $b ]; then
       echo "$a -eq $b : a 等于 b"
    fi
    
    if [ $a -ne $b ]; then
       echo "$a -ne $b: a 不等于 b"
    fi
    
    if [ $a -gt $b ];then
       echo "$a -gt $b: a 大于 b"
    fi
    
    if [ $a -lt $b ]; then
       echo "$a -lt $b: a 小于 b"
    fi
    
    if [ $a -ge $b ]; then
       echo "$a -ge $b: a 大于或等于 b"
    fi
    
    if [ $a -le $b ]; then
       echo "$a -le $b: a 小于或等于 b"
    fi
    
    # 另外一种方式直接符号比较
    if (( $a <= $b )); then
       echo "$a -le $b: a 小于或等于 b"
    fi
    
    if (( $a != $b )); then
       echo "$a -ne $b: a 不等于 b"
    fi
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.8.2. 字符串比较

    字符串比较有两种形式使用[],[[]],后者功能更强大,推荐后者。

    if [[ $a == z* ]]; then
    # $a只要以z开头即为true
    fi
    
    if [[ $a == "z*" ]]; then
    # $a为”z*“时才为true
    fi
    
    if [[ "$a" > "$b" ]]; then
    	echo "a greater than b"
    fi
    
    if [[ -z "$a" ]]; then
    	echo "$a length is 0"
    fi
    
    if [[ -n "$a" ]]; then
    	echo "$a length is not 0"
    fi
    
    if [[ "$a" ]]; then
    	echo "$a length is not null"
    fi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.8.3. 文件判断

    file="/var/test.sh"
    if [ -r $file ]; then
       echo "文件可读"
    fi
    
    if [ -w $file ]; then
       echo "文件可写"
    fi
    
    if [ -x $file ]; then
       echo "文件可执行"
    fi
    
    if [ -f $file ]; then
       echo "文件为普通文件"
    fi
    
    if [ -d $file ]; then
       echo "文件是个目录"
    fi
    
    if [ -s $file ]; then
       echo "文件不为空"
    fi
    
    if [ -e $file ]; then
       echo "文件存在"
    fi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2.8.4. 逻辑运算

    && 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
    || 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true
    ! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。

    # 3个命令都会执行
    cmd && cmd2 && cmd3
    # 只要有一个失败,后面的就不执行
    cmd || cmd2 || cmd3
    
    • 1
    • 2
    • 3
    • 4

    2.9. 循环

    2.9.1. for

    # 按空格分割遍历in后面的内容
    for loop in  1 2 3 4 5; do
        echo "The value is: $loop"
    done
    
    for str in This is a string; do
        echo $str
    done
    
    #${#array[@]}获取数组长度用于循环
    for(( i=0;i<${#array[@]};i++ )); do
        echo ${array[i]};
    done;
     
    
    # 不带数组下标
    for element in ${array[@]}; do
    	echo $element
    done
     
    # 带数组下标
    for i in "${!array[@]}";  do 
    	printf "%s\t%s\n" "$i" "${array[$i]}"  
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.9.2. while

    while 循环用于不断执行一系列命令,也用于从输入文件中读取数据。

    int=1
    while (( int <= 5 )); do
        echo $int
        (( int++ ))
    done
    
    # 读取用户键盘输出
    while read -r line; do
        echo "${line}" 
    done 
    
    # 读取文件util.sh,按行输出
    while read -r line; do
        echo "${line}" 
    done < util.sh
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.9.3. until

    until循环到满足条件

    a=0
    # 输出0到9
    until [ ! $a -lt 10 ]; do
       echo $a
       (( a++ ))
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.9.4. case

    read aNum
    case $aNum in
        1)  echo '1'
        ;;
        2)  echo ' 2'
        ;;
        3)  echo '3'
        ;;
        4)  echo '4'
        ;;
        *)  echo 'default'
        ;;
    esac
    
    name="mike"
    case "$name" in
       # ”mi"*匹配任何以mi开头的字符串
       "mi"*) echo "mike"
       ;;
       "jack") echo "jack"
       ;;
       "sam") echo "sam"
       ;;
    esac
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.9.5. break

    跳出所有循环。

    for(( i=0;i<${#array[@]};i++ )); do
    	if (( i -eq 2 )); then
    		echo ${array[i]}
    		break
    	fi
    done;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.9.6. continue

    退出当前循环,继续下一次循环

    for(( i=0;i<${#array[@]};i++ )); do
    	if (( i -eq 2 )); then
    		continue
    		echo ${array[i]}
    	fi
    done;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.9.7 select

    select in 循环用来增强交互性,它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。
    通常select是配合case来使用。

    echo "What is your favourite OS?"
    select name in "Linux" "Windows" "Mac OS" "UNIX" "Android"; do
        echo $name
    done
    echo "You have selected $name"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.10. 输入输出

    2.10.1. 输入

    2.10.1.1. read

    read [-optins] [variables]

    选项说明
    -aarray 把读取的数据赋值给数组 array,从下标 0 开始。
    -ddelimiter 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)。
    -e在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。
    -nnum 读取 num 个字符,而不是整行字符。
    -pprompt 显示提示信息,提示内容为 prompt。
    -r原样读取(Raw mode),不把反斜杠字符解释为转义字符。
    -s静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。
    -tseconds 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0
    -ufd 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。
    read -r -t 10 -s -p "Enter password in 20 seconds(once) : " pass1 && printf "\n"  
    read -r -t 10 -s -p "Enter password in 20 seconds(again): " pass2 && printf "\n" 
    if   [ "$pass1" == "$pass2" ]; then
        echo "Valid password"
    else
        echo "Invalid password"
    fi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2.10.1.2. 命令替换

    variable=$(cmd)
    shell运行命令替换符号中的命令,并将其输出赋值给变量variable。
    cmd可以是函数,可以是命令,也可以是可执行程序。

    var=$(echo "abcd")
    var=$(bc "1+2")
    a=$(echo "1+2" | bc)
    
    • 1
    • 2
    • 3

    2.10.2. 输出

    2.10.2.1. echo

    echo string,默认将字符串输出到标准终端控制台。

    # 普通输出
    echo "abcd"
    # 不换行输出
    echo "abc\r"
    # 输出变量
    echo $1
    # 默认转义
    echo "\"abc\""
    # 关闭转义
    echo "abc\n"
    # 覆盖输入到文件
    echo "abc">file
    # 附加输出到文件
    echo "abc">>file
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    2.10.2.2. printf

    printf,格式化输出。

    • %s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出。
    • %-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),任何字符都会被显示在 10 个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
    • %-4.2f 指格式化为小数,其中 .2 指保留2位小数。
    # printf默认不带换行
    printf "abcd\n"
    # format-string为双引号
    printf "%d %s\n" 1 "abc"
    # 保留位数
    printf "%-10s %-8s %-4.2f\n" lily 女 47.9876
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.11. 函数

    将代码封装成函数,提升代码复用性。

    [ function ] funname [()]
    {
        action;
        [return int;]
    }
    
    # 无参数无返回,推荐写function标识
    test() {
    	echo "This is function"
    }
    # 执行函数
    test
    
    # 有参数有返回值
    add() {
    	(( ret = $1 + $2 ))
    	return $ret
    }
    # 函数return值通过$?获取
    add 2 3
    echo $? 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.12. 输入/输出重定向

    大多数命令的输出默认是输出到终端的标准输出(STDOUT),错误信息则输出到标准错误输出(STDERR)中。

    • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
    • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
    • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
    命令说明
    command > file将输出重定向到 file。
    command < file将输入重定向到 file。
    command >> file将输出以追加的方式重定向到 file。
    n > file将文件描述符为 n 的文件重定向到 file。
    n >> file将文件描述符为 n 的文件以追加的方式重定向到 file。
    n >& m将输出文件 m 和 n 合并。
    n <& m将输入文件 m 和 n 合并。
    << tag将开始标记 tag 和结束标记 tag 之间的内容作为输入。
    # 重定位到文件file
    echo abc 1>file
    # 重定位到文件file,文件描述符1可以省略
    echo abc>file
    # 重定向文件输入
    command1 < file1
    # 不显示在屏幕上
    command > /dev/null
    屏蔽 stdout 和 stderr
    command > /dev/null 2>&1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.13. 引用文件

    一些公共代码可以封装到一个文件中,供不同项目使用

    # .和文件中必须要有空格
    . base.sh
    # 推荐
    source base.sh
    
    • 1
    • 2
    • 3
    • 4

    2.14. 执行文件

    2.14.1. 执行应用程序

    • 直接运行,如:fio
    • exec执行,执行完之后立即退出,不再执行下面的代码。如:exec fio

    2.14.2. 执行shell脚本

    • exec执行,创建子进程执行,执行完之后立即退出,不再执行下面的代码。如:exec base.sh
    • source base.sh,在当前进程中执行脚本,变量可以共用,阻塞式执行。
    • ./base.sh, 直接运行,子进程调用,阻塞式执行。
    • bash base.sh,命令参数调用,子进程调用,阻塞式执行。
    # base.sh
    # 输出进程号
    echo $$
    
    • 1
    • 2
    • 3
    # test.sh
    # 输出进程号
    echo $$
    source base.sh
    ./base.sh
    bash base.sh
    # 打印退出码
    echo $?
    exec ./base.sh
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.15. 组命令和子进程

    2.15.1. 组命令

    组命令,就是将多个命令划分为一组,或者看成一个整体。
    Shell 组命令的写法有两种:

    { command1; command2;. . .; }
    ( command1; command2;. . . )

    使用花括号{}时,花括号与命令之间必须要有一个空格,并且最后一个命令必须用一个分号或一个换行符结束组命令可以将多条命令的输出结果合并在一起,在使用重定向和管道时会特别方便。
    两种写法的重要不同:由{}包围的组命令在当前 Shell 进程中执行,由()包围的组命令会创建一个子Shell,所有命令都会在这个子 Shell 中执行。
    在子 Shell 中执行意味着,运行环境被复制给了一个新的 shell 进程,当这个子 Shell 退出时,新的进程也会被销毁,环境副本也会消失,
    所以在子 Shell 环境中的任何更改都会消失(包括给变量赋值)。因此,在大多数情况下,除非脚本要求一个子 Shell,
    否则使用{}比使用()更受欢迎,并且{}的进行速度更快,占用的内存更少。

    2.15.2. 子进程

    子进程的概念是由父进程的概念引申而来的。在 Linux 系统中,系统运行的应用程序几乎都是从 init(pid为 1 的进程)进程派生而来的,所有这些应用程序都可以视为 init 进程的子进程,而 init 则为它们的父进程。

    Shell 脚本是从上至下、从左至右依次执行的,即执行完一个命令之后再执行下一个。如果在 Shell 脚本中遇到子脚本(即脚本嵌套,但是必须以新进程的方式运行)或者外部命令,就会向系统内核申请创建一个新的进程,以便在该进程中执行子脚本或者外部命令,这个新的进程就是子进程。子进程执行完毕后才能回到父进程,才能继续执行父脚本中后续的命令及语句。

    使用pstree -p命令就可以看到 init 及系统中其他进程的进程树信息(包括 pid):
    systemd(1)─┬─ModemManager(796)─┬─{ModemManager}(821)
    │ └─{ModemManager}(882)
    ├─NetworkManager(975)─┬─{NetworkManager}(1061)
    │ └─{NetworkManager}(1077)
    ├─abrt-watch-log(774)
    ├─abrt-watch-log(776)
    ├─abrtd(773)
    ├─accounts-daemon(806)─┬─{accounts-daemon}(839)
    │ └─{accounts-daemon}(883)
    ├─alsactl(768)
    ├─at-spi-bus-laun(1954)─┬─dbus-daemon(1958)───{dbus-daemon}(1960)
    │ ├─{at-spi-bus-laun}(1955)
    │ ├─{at-spi-bus-laun}(1957)
    │ └─{at-spi-bus-laun}(1959)
    ├─at-spi2-registr(1962)───{at-spi2-registr}(1965)
    ├─atd(842)
    ├─auditd(739)─┬─audispd(753)─┬─sedispatch(757)
    │ │ └─{audispd}(759)
    │ └─{auditd}(752)

    2.15.3. 如何检测子shell与子进程

    echo $$输出当前进程ID,echo $PPID输出父shell ID。
    除了 $,Bash 还提供了另外两个环境变量——SHLVL 和 BASH_SUBSHELL,用它们来检测子 Shell 非常方便。
    SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,每次进入一层普通的子进程,SHLVL 的值就加 1。而 BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(sub shell)嵌套深度的累加器,每次进入一层子 Shell,BASH_SUBSHELL 的值就加 1。

    2.16. 管道

    管道的主要是作用是将一个命令的输出作为另一个命令的输入。管道符号为"|"。

    echo "1+3" | bc
    dmesg | grep "inth"
    
    • 1
    • 2

    2.17. 后台执行

    后台执行,也就是异步执行,非阻塞式的。

    • 针对单条命令,可以使用nohup加&

    nohup cmd &

    • 针对一组命令可以使用

    (cmd1; cmd2)&

    • 针对一段代码,可以使用
    {
    	cmd1
    	cmd2
    	sleep
    	cmd3
    }&
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 等待所有进程结束

    wait

    2.18. 信号与捕获

    信号(Signal):信号是在软件层次上对中断机制的一种模拟,通过给一个进程发送信号,执行相应的处理函数(即捕获信号)。
    一种是标准信号,编号1-31,称为非可靠信号(非实时),不支持队列,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,如果第一个信号没有处理完,第二个信号将会丢弃。其中比较典型的有一种是内核检测到系统事件,比如键盘输入CTRL+C会发送SIGINT信号。
    另一种是通过系统调用kill命令来向一个进程发送信号。

    2.18.1. 设置信号捕获

    设置信号号为50的信息捕获

    trap “echo catch signal” 50

    2.18.2. 发送信息

    发送信息使用kill指令
    给当前进程发送50号信息

    kill -n 50 $$

    2.18.3. 示例

    custom_sig=50
    trap "echo abc" $custom_sig   
    sleep 2
    kill -n $custom_sig $$ 
    
    # 屏蔽Ctrl+C功能,响应必须为空
    trap "" INT  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.19. 退出码

    脚本退出码通过exit函数设置,可以通过$?获取退出码。
    $?也可以获取第三方可执行程序的退出码。
    退出码,默认0为正常,非0为异常。

    2.20. 常用的命令行工具

    unix-like哲学是以进程来作为模块完成功能,所以unix-like系统中有大量的功能都是小工具来完成的。

    • sleep, 以秒延时时间,支持小数
    • bc, 计算模块
    • grep,正则匹配模块
    • awk, 文本操作模块
    • sed, 文本遍历替换
    • at, 定时任务
    • cat,head,tail,文本读取模块
    • gzip,解压缩模块
    • cp,scp,拷贝,远程拷贝模块
    • curl,网络通信模块
    • chmod,文本模式修改模块
    • wget,下载模块
    • touch,创建文件
    • mv, 移动文件
    • rm, 删除文件
    • find, 查找功能
    • date,时间功能
    • ln, 软链接
    • ssh, ssh通信

    2.21. 内置环境变量

    变 量功 能
    BASH调用 Shell 的完整文件名
    BASHOPTS启用 Bash Shell 的选项列表
    BASHPID当前 Bash Shell 的进程 ID
    BASH_ALIASES含有当前所用别名的关联数组
    BASH_ARGC当前子函数或 Shell 脚本中的参数总数的数组变量
    BASH_ARCV当前子函数或 Shell 脚本中的参数的数组变量
    BASHCMDS关联数组,包括 Shell 执行过的命令的所在位置
    BASH_COMMAND当前正在被执行的命令名
    BASH_ENV如果设置,每个 Bash 脚本都会尝试在运行前执行由该变量定义的起始文件
    BASH_EXECUTION_STRING在 Bash -c 命令行选项中用到的命令
    BASH_LINENO含有脚本中每个命令的行号的数组变量
    BASH_REMATCH只读数组,含有与指定的正则表达式匹配的文本元素的数组
    BASH_SOURCE含有 Shell 中已声明函数所在源文件名的数组变量
    BASH_SUBSHELL当前 Shell 生成的子 Shell 数目
    BASH_VERS INFO含有当前运行的 Bash Shell 的主版本号和次版本号的数组变量
    BASH_VERS ION当前运行的 Bash Shell 的版本号
    BASH_XTRACEFD当设置一个有效的文件描述符整数时,跟踪输出生成,并与诊断和错误信息分离开文件描述符必须设置 -x 启动
    COLUMNS当前 Bash Shell 实例使用的终端的宽度
    COMP_CWORD变量 COMP_WORDS 的索引值,COMP_WORDS 包含当前光标所在的位置
    COMP_KEY调用 Shell 函数补全功能的按键
    COMP_LINE当前命令行
    COMP POINT当前光标位置相对于当前命令起始位置的索引
    COMP_TYPE一个整数值,表示所尝试的补全类型,完成 Shell 函数的补全
    COMP_WORDBREAKS在进行单词补全时用作单词分隔符的一组字符
    COMP_WORDS含有当前命令行上所有单词的数组变量
    COMPREPLY含有由 Shell 函数生成的可能补全代码的数组变量
    COPROC占有未命名的协程的 I/O 文件描述符的数组变量
    DIRSTACK含有目录栈当前内容的数组变量。
    EMACS如果设置了该环境变量,则 Shell 认为其使用的是 emacs Shell 缓冲区,同时禁止进行编辑功能
    ENV如果设置了该环境变量,每个 Bash 脚本在运行之前都会执行由该环境变量所定义的起始文件
    EUID当前用户的有效用户 ID (数字形式)
    FCEDIT fc命令使用的默认编辑器
    FIGNORE用分隔的后缀名列表,在文件名补全时会被忽略
    FUNCNAME当前执行的 Shell 函数的名称
    FUNCNEST当设置成非 0 值时,嵌套函数的最髙层级
    GLOBIGNORE以分隔的模式列表,定义了在进行文件名扩展时要忽略的文件名集合
    GROUPS含有当前用户属组列表的数组变量
    histchars控制历史记录展幵的字符(最多可有 3 个字符)
    HISTCMD当前命令在历史记录中的编码
    HISTCONTROL控制哪些命令留在历史记录列表中
    HISTFILE保存 Shell 历史记录列表的文件名(默认是 .Bash_history )
    HISTFILESIZE保存在历史文件中的最大行数
    HISTIGNORE以分隔的模式列表,用来决定哪些命令会被忽略
    HISTSIZE最多在历史文件中保存多少条命令
    HISTIMEFORMAT如果设置且非空,用作格式化字符,决定历史文件条目的时间戳
    HOSTFILE含有Shell在补全主机名时读取的文件的名称
    HOSTNAME当前主机的名称
    HOSTTYPE当前运行Bash Shell的机器
    IGNOREEOFShell在退出前必须收到连续的EOF字符的数量。如果这个值不存在,则默认是1
    INPUTRCreadline初始化文件名(默认是.inputrc )
    LANGShell的语言环境分类
    LC_ALL定义一个语言环境分类,它会覆盖LANG变量
    LC_COLLATE设置对字符串值排序时用的排序规则
    LC_CTYPE决定在进行文件名扩展和模式匹配时,如何解释其中的字符
    LC_MESSAGES决定解释前置美元符($)的双引号字符串的语言环境设置
    LC_NUMERIC决定格式化数字时所使用的语言环境设置
    LINENO脚本中当前执行代码的行号
    LINES定义了终端上可见的行数
    MACHTYPE用“cpu-公司-系统”格式定义的系统类型
    MAILCHECKShell 查看邮件的频率(以 s 为单位,默认值是 60s)
    MAPFILE含有 mapfile 命令所读入文本的数组,当没有给出变量名的时候,使用该环境变量
    OLDPWDShell之前的工作目录
    OPTERR设置为1时,Bash Shell会显示getopts命令产生的错误
    OSTYPE定义了 Shell所在的操作系统
    PIPESTATUS含有前台进程退出状态列表的数组变量
    POSIXLY_CORRECT如果设置了该环境变量,Bash 会以 POSIX 模式启动
    PPIDBash Shell 父进程的 PID
    PROMPT COMMAND如果设置该环境变量,在显示命令行主提示符之前会执行这条命令
    PS3select命令的提不符
    PS4如果使用了 Bash 的 -x 选项,在命令行显示之前显示的提示符
    PWD当前工作目录
    RANDOM返回一个 0〜32767 的随机数,对其赋值可作为随机数生成器的种子
    READLINE_LINE当使用 bind -x 命令时,保存了 readline 行缓冲区中的内容
    READLINE_POINT当使用 bind -x 命令时,当前 readline 行缓冲区的插入点位置
    REPLYread 命令的默认变量
    SECONDS自从 Shell 启动到现在的秒数,对其赋值将会重置计时器
    SHELLBash Shell 的全路径名
    SHELLOPTS已启用 Bash Shell 选项列表,用“”分隔开
    SHLVL表明 Shell 的层级,每次启动一个新的 Bash Shell 时该值增加1
    TIMEFORMAT指定 Shell 显示的时间值的格式
    TMOUTselect 和 read 命令在没输入的情况下等待多久(以 s 为单位)。默认值为零,表示无限长
    TMPDIR如果设置成目录名,Shell 会将其作为临时文件目录
    UID当前用户的真实用户 ID (数字形式)

    3. 应用

    3.1. 启动配置

    打开终端时系统自动调用:/etc/profile 或 ~/.bashrc。其中profile文件是所有用户共享,而bashrc则是每个用户一个,存放在home的当前用户目录中。
    我们根据情况可以选择在profile或.bashrc中创建环境变量或是一些简短的别名。

    export kconfig=config_gyf
    alias ll='ls -l --color=auto'
    alias ls='ls --color=auto'
    # 因为.bashrc只在启动的时候执行一次,如果再立即使用,则需要手动执行一下
    source .bashrc
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2. 遍历当前目录所有文件

    for file in ./* ;do
        echo "$file"
    done
    
    # 包括子目录
    traverse_files() {
        for file in "$1"*; do
            if [ -d "$1""$file" ]; then
                traverse_files "$1""$file"/
            else
                echo "$1""$file" 
            fi
        done
     } 
    
    traverse_files 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.3. 暂停

    get_char()
    {
        SAVEDSTTY=$(stty -g)
        stty -echo
        stty cbreak
        dd if=/dev/tty bs=1 count=1 2> /dev/null
        stty -raw
        stty echo
        stty "$SAVEDSTTY"
    }
    
    # for debug
    enable_pause=1
    ys::pause()
    {
        if [ "x$1" != "x" ] ;then
            echo "$1"
        fi
        if [ $enable_pause -eq 1 ];then
            echo "Press any key to continue!"
            get_char
        fi
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3.4. 调试

    可以在脚本开始设置如下代码进行调试。

    • set -v,显示执行的原始代码。
    • set +v, 关闭显示,默认是关闭的。
    • set -x, 打开脚本执行的详细过程,变量会展开显示。
    • set +x, 关闭脚本执行显示,默认关闭。
      可以在执行脚本时,带参数启动调试。
    # 开启显示脚本执行的详细过程
    sudo bash -x test.sh
    
    • 1
    • 2
  • 相关阅读:
    新火种AI|挑战谷歌,OpenAI要推出搜索引擎?
    【云原生】加强理解Pod资源控制器
    SignalR示例
    linux 部署dns正向解析服务,照做就可以
    python│蓝桥杯省赛真题星期一问题
    win10修改截图快捷键
    springboot 引入 mybatis -plus
    codeforces 726 E2
    Arduino PLC IDE
    批量替换文件夹下的图片后缀名
  • 原文地址:https://blog.csdn.net/feihe027/article/details/126771784