• shell脚本学习笔记2


    小试牛刀(Ⅱ)

    1.7 别名

    当我们执行一些较长的命令时,重复的输入会非常麻烦,这时我们可以为这类命令创建一个简短的别名,创建别名使用 alias 命令。

    # 创建别名
    alias new_command='command sequence' 
    
    alias install='sudo apt-get install' 
    
    # 删除原始文件,同时在backup目录中保留副本。
    alias rm='cp $@ ~/backup && rm $@' 
    
    # 列出当前定义的所有别名
    alias
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • alias 命令的效果只是暂时的。一旦关闭当前终端,所有设置过的别名就失效了。为了使别名在所有的shell中都可用,可以将其定义放入~/.bashrc文件中。每当一个新的交互式shell进程生成时,都会执行 ~/.bashrc中的命令。

      echo 'alias cmd="command seq"' >> ~/.bashrc

    • 如果需要删除别名,只需将其对应的定义(如果有的话)从~/.bashrc中删除,或者使用unalias 命令。也可以使用 alias example= ,这会取消别名 example

      unalias example 或者 alias example=

    • 创建别名时,如果已经有同名的别名存在,那么原有的别名设置将被新的设置取代。

    1.8 获取并设置日期及延时

    延时可以用来在程序执行过程中等待一段时间(比如1秒),或是每隔几秒钟(或是几个月)监督某项任务。日期能够以多种格式呈现。在系统内部,日期被存储成一个整数,其取值为自1970年1月1
    日0时0分0秒起所流逝的秒数。这种计时方式称为纪元时或Unix时间。 下边是一些常见的使用方式:

    # 打印日期
    date     # Fri 19 Aug 2022 04:04:08 AM UTC
    
    # 打印纪元时
    date +%s    # 1660882402
    
    # 将日期转换为纪元时
    date --date "Wed mar 15 08:09:16 EDT 2017" +%s     # 1489579718
    
    # 打印某天属于星期几
    date --date "Jan 20 2001" +%A     # Saturday 
    date +%A
    
    # 设置系统日期
    date -s "21 June 2009 11:01:22" 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上述例子中,通过使用带前缀 + 的字符串,可以指定所需要打印的日期信息,类似的字符串还有如下:

    日期内容格式
    工作日(weekday)%a(例如:Sat)
    %A(例如:Saturday)
    %b(例如:Nov)
    %B(例如:November)
    %d(例如:31)
    特定格式日期(mm/dd/yy)%D(例如:10/18/10)
    %y(例如:10)
    %Y(例如:2010)
    小时%I或%H(例如:08)
    分钟%M(例如:33)
    %S(例如:10)
    纳秒%N(例如:695208515)
    Unix纪元时%s(例如:1290049486)

    对一段代码进行计时:

    #!/bin/bash 
    #文件名: time_take.sh 
    start=$(date +%s) 
    commands; 
    statements; 
    end=$(date +%s) 
    difference=$(( end - start)) 
    echo Time taken to execute commands is $difference seconds. 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.9 调试脚本

    所有编程语言都应该实现的一个特性就是在出现始料未及的情况时,能够生成跟踪信息。调试信息可以帮你弄清楚是什么原因使得程序行为异常。

    Bash 的调试主要有以下三种调试方式:

    一、使用 Bash 内建的调试工具:

    • 使用 -x 选项运行脚本:bash -x script.sh,会打印出每行命令以及当前的状态。

    • 使用 set -xset +x 可以对脚本的部分内容进行调试。

    #!/bin/bash 
    #文件名: debug.sh 
    for i in {1..6}; 
    do 
        set -x 
        echo $i 
        set +x 
    done 
    echo "Script executed" 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    -x+x 之间的区域进行调试

    二、通过 _DEBUG 环境变量生成调试信息

    #!/bin/bash 
    function DEBUG() 
    { 
        [ "$_DEBUG" == "on" ] && $@ || : 
    } 
    for i in {1..10} 
    do 
      DEBUG echo "I is $i" 
    done 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    _DEBUG=on ./script.sh ,设置 _DEBUG=on 来运行

    三、使用 shebang 进行调试

    把 shebang 从 #!/bin/bash 改成 #!/bin/bash -xv,这样一来,不用任何其他选项就可以启用调试功能了。

    sh -x testScript.sh 2> debugout.txt 可以将调试的信息输出到指定的文件。

    1.10 函数和参数

    函数的定义包括function命令、函数名、开/闭括号以及包含在一对花括号中的函数体。

    # 函数的定义
    function fname()
    {
    	statements;
    }
    
    fname()
    {
    	statements;
    }
    
    # 函数的调用,直接使用函数名调用即可
    fname;
    
    # 函数参数可以按位置访问, $1 是第一个参数, $2 是第二个参数,
    fname arg1 arg2;
    fname() 
    { 
        echo $1, $2;    #访问参数1和参数2 
        echo "$@";      #以列表的方式一次性打印所有参数 
        echo "$*";      #类似于$@,但是所有参数被视为单个实体 
        return 0;       #返回值 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 与其它语言不同的是,bash 的函数不需要形式参数,而是通过以下特定的方式访问。

      $0 是脚本名称。
      $1 是第一个参数。
      $2 是第二个参数。
      $n 是第n个参数。
      “$@” 被扩展成 “$1” “$2” “$3” 等。
      “$*” 被扩展成 “$1c$2c$3” ,其中 c 是IFS的第一个字符。

    1.11 将一个命令的输出发送给另一个命令

    Unix shell脚本最棒的特性之一就是可以轻松地将多个命令组合起来生成输出。一个命令的输出可以作为另一个命令的输入,而这个命令的输出又会传递至下一个命令,以此类推。

    命令输入通常来自于 stdin 或参数。输出可以发送给 stdout 或 stderr 。当我们组合多个命令时,通常将 stdin 用于输入, stdout 用于输出。

    在这种情况下,这些命令被称为过滤器(filter)。我们使用管道(pipe)连接每个过滤器,管道操作符是 | 。例如:comd1 | comd2 | comd3

    我们通常使用管道并配合子shell的方式来组合多个命令的输出。

    # 1. 组合两个命令
    ls | cat -n > out.txt 
    
    # 2. 子 shell 法
    cmd_output=$(ls | cat -n) 
    echo $cmd_output 
    
    # 3. 反引用
    cmd_output=`ls | cat -n`
    echo $cmd_output 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. ls (列出当前目录内容)的输出被传给 cat -n ,后者为通过 stdin 所接收到的输入内容加上行号,然后将输出重定向到文件out.txt。
    2. 将命令的输出赋值给变量,之后以命令行参数的形式传递给下一跳命令
    3. 类似子 shell 法,也是先将命令的输出先赋值给变量

    1.11.1 利用子 shell 生成一个独立的进程

    子shell本身就是独立的进程。可以使用 () 操作符来定义一个子shell。

    pwd
    (cd /bin; ls)
    
    • 1
    • 2

    当命令在子shell中执行时,不会对当前shell造成任何影响;所有的改变仅限于该子shell内。例如,当用 cd 命令改变子shell的当前目录时,这种变化不会反映到主shell环境中。

    1.11.2 通过引用子shell的方式保留空格和换行符

    假设我们使用子shell或反引用的方法将命令的输出保存到变量中,为了保留输出的空格和换行符( \n ),必须使用双引号。

    $ cat text.txt 
    1 
    2 
    3 
     
    $ out=$(cat text.txt) 
    $ echo $out 
    1 2 3     # 丢失了1、2、3中的\n  
     
    $ out="$(cat text.txt)" 
    $ echo $out 
    1 
    2 
    3 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.12 在不按下回车键的情况下读入 n 个字符

    Bash命令 read 能够从键盘或标准输入中读取文本。

    # 从输入中读取n个字符并存入变量 var
    read -n 2 var
    
    # 用无回显的方式读取密码: 
    read -s var
    
    # 显示提示信息
    read -p "Enter input:" var 
    
    # 给定的时间内输入,单位是 秒
    read -t 2 var
    
    # 用特定的定界符作为输入行的结束: 
    read -d ":" var 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.13 持续运行命令直到执行成功

    有时候命令只有在满足某些条件时才能够成功执行。例如,在下载文件之前必须先创建该文件。这种情况下,你可能希望重复执行命令,直到成功为止。

    首先我们可以定义如下形式的函数:

    repeat() 
    { 
      while true 
      do 
        $@ && return 
      done 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    调用该函数会重复执行 while 循环,直到命令执行成功,才返回。

    为了方便可以将该函数放入shell的rc文件,更便于使用。

    repeat() { while true; do $@ && return; done }

    但上述函数的一个缺点是每执行一次 while 循环都会生成一个新的进程,因为在 shell 中 true 是通过 /bin 目录下一个二进制文件实现的。

    为了避免这种情况,可以使用shell的内建命令 : ,该命令的退出状态总是为0:

    repeat() { while :; do $@ && return; done }

    如果我们事先知道某条命令的执行需要耗费一定的时间,比如执行下载某个文件命令,那么直接执行上述命令会造成的一个结果是会产生大量对目标服务器的下载请求。有可能会被目标服务器拉黑,因此我们可以在上述函数中加入一段时延,

    repeat() { while :; do $@ && return; sleep 30; done }

    没 30s 执行一次

    1.13.1 内建命令和外部命令

    shell 中的命令分为内建命令和外部命令。前者是构建在shell内部;后者是一个独立的文件(可以是二进制文件,也可以是一个脚本)

    **内部命令:**实际上是shell程序的一部分,其中包含的是一些比较简单的linux系统命令,这些命令由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留在系统内存中。内部命令是写在bash源码里面的,其执行速度比外部命令快,因为解析内部命令shell不需要创建子进程。比如:exit,history,cd,echo等。

    外部命令:它的执行比较复杂,shell进程会fork一个子进程,父进程随后挂起,然后在子进程中exec加载外部文件,子进程返回后,父进程才继续执行。

    判断方式:我们可以使用 type命令判断某个命令是否是内建命令

    type cd # cd is a shell builtin
    type ifconfig # ifconfig is /usr/sbin/ifconfig
    
    • 1
    • 2

    1.13.2 shell 中的条件判断

    在 shell 中每一条命令或者表达式执行完之后都会返回一个状态码,在条件判断的时候会根据该状态码进行判断,0 被认为为真,1 被认为为假。可以通过$?获取最近执行命令或表达式的状态码

    true
    $? # 0
    
    false
    $? # 1
    
    [ 1 == 1 ]
    $? # 0
    
    [ 1 == 2 ]
    $? # 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.14 字段分隔符与迭代器

    1.14.1 字段分隔符

    内部字段分隔符(Internal Field Separator,IFS)是shell脚本编程中的一个重要概念。作为分隔符,IFS有其特殊用途。它是一个环境变量,其中保存了用于分隔的字符。它是当前shell环境使用的默认定界字符串。默认情况下,系统的 IFS 默认为空白字符(换行符、制表符或者空格)

    #!/bin/bash
    
    # IFS 设置为逗号
    data="name, gender,rollno,location"
    oldIFS=$IFS 
    IFS=","     #IFS现在被设置为, 
    for item in $data; 
    do 
        echo Item: $item 
    done 
     
    IFS=$oldIFS 
    
    # Item: name
    # Item:  gender
    # Item: rollno
    # Item: location
    
    # -------------------------
    
    # IFS 为默认值
    data="name, gender,rollno,location"
    oldIFS=$IFS 
    for item in $data; 
    do 
        echo Item: $item 
    done 
     
    IFS=$oldIFS 
    # Item: name,
    # Item: gender,rollno,location
    
    • 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

    1.14.2 迭代器

    for 循环

    #!/bin/bash
    
    for var in {a..z}; do
       echo ${var}
    done
    
    for ((i=0;i<10;++i)); do
       echo ${i}
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    **注意:**运行的时候需要使用 bash xxx.sh,sh xxx.sh 不可以,不支持 {…} 扩展。

    sh(Bourne Shell)是一个早期的重要shell,1978年由史蒂夫·伯恩编写,并同Version 7 Unix一起发布。

    bash(Bourne-Again Shell)是一个为GNU计划编写的Unix shell。1987年由布莱恩·福克斯创造。主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,是各种Linux发行版标准配置的Shell,在Linux系统上/bin/sh往往是指向/bin/bash的符号链接。

    while 循环

    #!/bin/bash
    
    i=0
    while(( ${i}<10 ))
    do
       echo ${i}
       let "i++"
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    until 循环

    x=0; 
    until [ $x -eq 9 ];    #条件是[$x -eq 9 ] 
    do 
        let x++; echo $x; 
    done 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当条件不为真时就一直循环,直到条件为真时截止

    1.15 比较和测试

    if

    if condition; 
    then 
        commands; 
    fi
    
    if condition;  
    then 
        commands; 
    else if condition; then 
        commands; 
    else 
        commands; 
    fi 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    算术比较

    [ $var -eq 0 ] #当$var等于0时,返回真 [ $var -ne 0 ] #当$var不为0时,返回真

    需要注意,在 [ 或 ] 与操作数之间有一个空格。如果忘记了这个空格,脚本就会报错。

    -gt :大于

    -lt :小于

    -ge :大于或等于。

    -le :小于或等于。

    -a :逻辑与

    -o :逻辑或

    !:逻辑非

    文件系统相关

    [ -f $file_var ] :如果给定的变量包含正常的文件路径或文件名,则返回真
    [ -x $var ] :如果给定的变量包含的文件可执行,则返回真
    [ -d $var ] :如果给定的变量包含的是目录,则返回真
    [ -e $var ] :如果给定的变量包含的文件存在,则返回真
    [ -c $var ] :如果给定的变量包含的是一个字符设备文件的路径,则返回真
    [ -b $var ] :如果给定的变量包含的是一个块设备文件的路径,则返回真
    [ -w $var ] :如果给定的变量包含的文件可写,则返回真
    [ -r $var ] :如果给定的变量包含的文件可读,则返回真
    [ -L $var ] :如果给定的变量包含的是一个符号链接,则返回真

    字符串比较

    进行字符串比较时,最好用双中括号,因为有时候采用单个中括号会产生错误。

    注意,双中括号是Bash的一个扩展特性。如果出于性能考虑,使用ash或dash来运行脚本,那么将无法使用该特性。

    # 测试两个字符串是否相等
    [[ $str1 = $str2 ]]
    [[ $str1 == $str2 ]]
    [[ $str1 != $str2 ]]
    
    # 按字典序比较
    [[ $str1 > $str2 ]]
    [[ $str1 < $str2 ]]
    
    # 判断是否为空串
    [[ -z $str1 ]]
    
    # 判断是否为非空串
    [[ -n $str1 ]]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 等于号前后需要加上空格,不然会变成赋值操作

    上述括号可以使用 test 命令进行替代,避免过多使用 [],增加代码的可读性。

    if  test $var -eq 0 ; then echo "True"; fi 
    
    • 1

    test 是一个外部程序,需要衍生出对应的进程,而 [ 是Bash的一个内部函数,因此后者的执行效率更高。 test 兼容于Bourne shell、ash、dash等。
    的一个扩展特性。如果出于性能考虑,使用ash或dash来运行脚本,那么将无法使用该特性。

    # 测试两个字符串是否相等
    [[ $str1 = $str2 ]]
    [[ $str1 == $str2 ]]
    [[ $str1 != $str2 ]]
    
    # 按字典序比较
    [[ $str1 > $str2 ]]
    [[ $str1 < $str2 ]]
    
    # 判断是否为空串
    [[ -z $str1 ]]
    
    # 判断是否为非空串
    [[ -n $str1 ]]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 等于号前后需要加上空格,不然会变成赋值操作

    上述括号可以使用 test 命令进行替代,避免过多使用 [],增加代码的可读性。

    if  test $var -eq 0 ; then echo "True"; fi 
    
    • 1

    test 是一个外部程序,需要衍生出对应的进程,而 [ 是Bash的一个内部函数,因此后者的执行效率更高。 test 兼容于Bourne shell、ash、dash等。

  • 相关阅读:
    乐高Studio打开Solidworks零件/装配体 (sw另存obj文件)
    [ 隧道技术 ] 反弹shell的集中常见方式(二)bash反弹shell
    VS2019下生成dll动态库及其引入实验
    【小呆的热力学笔记】热力学第一定律
    Android帧绘制流程深度解析 (二)
    suging闲谈-netty 的异步非阻塞IO线程与业务线程分离
    .NET宝藏API之:OutputFormatter,格式化输出对象
    WebSocket
    PythonOpenCV随机粘贴图像
    c语言练习84:动态内存管理
  • 原文地址:https://blog.csdn.net/weixin_43786143/article/details/126658186