• Linux Shell高级技巧(二)


    七、非直接引用变量:

          在Shell中提供了三种为标准(直接)变量赋值的方式:
          1. 直接赋值。
          2. 存储一个命令的输出。
          3. 存储某类型计算的结果。
          然而这三种方式都是给已知变量名的变量赋值,如name=Stephen。但是在有些情况下,变量名本身就是动态的,需要依照运行的结果来构造变量名,之后才是为该变量赋值。这种变量被成为动态变量,或非直接变量。
          /> cat > test7.sh
          #!/bin/sh
          work_dir=`pwd`
          #1. 由于变量名中不能存在反斜杠,因此这里需要将其替换为下划线。
          #2. work_dir和file_count两个变量的变量值用于构建动态变量的变量名。
          work_dir=`echo $work_dir | sed 's/\//_/g'`
          file_count=`ls | wc -l`
          #3. 输出work_dir和file_count两个变量的值,以便确认这里的输出结果和后面构建的命令名一致。
          echo "work_dir = " $work_dir
          echo "file_count = " $file_count
          #4. 通过eval命令进行评估,将变量名展开,如${work_dir}和$file_count,并用其值将其替换,如果不使用eval命令,将不会完成这些展开和替换的操作。最后为动态变量赋值。
          eval BASE${work_dir}_$file_count=$(ls $(pwd) | wc -l)
          #5. 先将echo命令后面用双引号扩住的部分进行展开和替换,由于是在双引号内,仅完成展开和替换操作即可。
          #6. echo命令后面的参数部分,先进行展开和替换,使其成为$BASE_root_test_1动态变量,之后在用该变量的值替换该变量本身作为结果输出。
          eval echo "BASE${work_dir}_$file_count = " '$BASE'${work_dir}_$file_count
          CTRL+D
          /> . ./test7.sh
          work_dir =  _root_test
          file_count =  1
          BASE_root_test_1 = 1
       
    八、在循环中使用管道的技巧:

          在Bash Shell中,管道的最后一个命令都是在子Shell中执行的。这意味着在子Shell中赋值的变量对父Shell是无效的。所以当我们将管道输出传送到一个循环结构,填入随后将要使用的变量,那么就会产生很多问题。一旦循环完成,其所依赖的变量就不存在了。
          /> cat > test8_1.sh
          #!/bin/sh
          #1. 先将ls -l命令的结果通过管道传给grep命令作为管道输入。
          #2. grep命令过滤掉包含total的行,之后再通过管道将数据传给while循环。
          #3. while read line命令从grep的输出中读取数据。注意,while是管道的最后一个命令,将在子Shell中运行。
          ls -l | grep -v total | while read line
          do
              #4. all变量是在while块内声明并赋值的。
              all="$all $line"
              echo $line
          done
          #5. 由于上面的all变量在while内声明并初始化,而while内的命令都是在子Shell中运行,包括all变量的赋值,因此该变量的值将不会传递到while块外,因为块外地命令是它的父Shell中执行。
          echo "all = " $all
          CTRL+D
          /> ./test8_1.sh
          -rw-r--r--.  1 root root 193 Nov 24 11:25 outfile
          -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
          -rwxr-xr-x. 1 root root 108 Nov 24 12:48 test8_1.sh
          all =

          为了解决该问题,我们可以将while之前的命令结果先输出到一个临时文件,之后再将该临时文件作为while的重定向输入,这样while内部和外部的命令都将在同一个Shell内完成。
          /> cat > test8_2.sh
          #!/bin/sh
          #1. 这里我们已经将命令的结果重定向到一个临时文件中。
          ls -l | grep -v total > outfile
          while read line
          do
              #2. all变量是在while块内声明并赋值的。
              all="$all $line"
              echo $line
              #3. 通过重定向输入的方式,将临时文件中的内容传递给while循环。
          done < outfile
          #4. 删除该临时文件。
          rm -f outfile
          #5. 在while块内声明和赋值的all变量,其值在循环外部仍然有效。
          echo "all = " $all
          CTRL+D
          /> ./test8_2.sh
          -rw-r--r--.  1 root root   0 Nov 24 12:58 outfile
          -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
          -rwxr-xr-x. 1 root root 140 Nov 24 12:58 test8_2.sh
          all =  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_2.sh

          上面的方法只是解决了该问题,然而却带来了一些新问题,比如临时文件的产生容易导致性能问题,以及在脚本异常退出时未能及时删除当前使用的临时文件,从而导致生成过多的垃圾文件等。下面将再介绍一种方法,该方法将同时解决以上两种方法同时存在的问题。该方法是通过HERE-Document的方式来替代之前的临时文件方法。
          /> cat > test8_3.sh
          #!/bin/sh
          #1. 将命令的结果传给一个变量    
          OUTFILE=`ls -l | grep -v total`
          while read line
          do
              all="$all $line"
              echo $line
          done <<EOF
          #2. 将该变量作为该循环的HERE文档输入。
          $OUTFILE
          EOF
          #3. 在循环外部输出循环内声明并初始化的变量all的值。
          echo "all = " $all
          CTRL+D
          /> ./test8_3.sh
          -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
          -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh
          all =  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh
       
    九、自链接脚本:

          通常而言,我们是通过脚本的命令行选项来确定脚本的不同行为,告诉它该如何操作。这里我们将介绍另外一种方式来完成类似的功能,即通过脚本的软连接名来帮助脚本决定其行为。
          /> cat > test9.sh
          #!/bin/sh
          #1. basename命令将剥离脚本的目录信息,只保留脚本名,从而确保在相对路径的模式下执行也没有任何差异。
          #2. 通过sed命令过滤掉脚本的扩展名。
          dowhat=`basename $0 | sed 's/\.sh//'`
          #3. 这里的case语句只是为了演示方便,因此模拟了应用场景,在实际应用中,可以为不同的分支执行不同的操作,或将某些变量初始化为不同的值和状态。
          case $dowhat in
          test9)
              echo "I am test9.sh"
              ;;
          test9_1)
              echo "I am test9_1.sh."
              ;;
          test9_2)
              echo "I am test9_2.sh."
              ;;
          *)
              echo "You are illegal link file."
              ;;
          esac
          CTRL+D
          /> chmod a+x test9.sh
          /> ln -s test9.sh test9_1.sh
          /> ln -s test9.sh test9_2.sh
          /> ls -l
          lrwxrwxrwx. 1 root root   8 Nov 24 14:32 test9_1.sh -> test9.sh
          lrwxrwxrwx. 1 root root   8 Nov 24 14:32 test9_2.sh -> test9.sh
          -rwxr-xr-x. 1 root root 235 Nov 24 14:35 test9.sh
          /> ./test9.sh
          I am test9.sh.
          /> ./test9_1.sh
          I am test9_1.sh.
          /> ./test9_2.sh
          I am test9_2.sh.

    十、Here文档的使用技巧:

          在命令行交互模式下,我们通常希望能够直接输入更多的信息,以便当前的命令能够完成一定的自动化任务,特别是对于那些支持自定义脚本的命令来说,我们可以将脚本作为输入的一部分传递给该命令,以使其完成该自动化任务。
          #1. 通过sqlplus以dba的身份登录Oracle数据库服务器。
          #2. 在通过登录后,立即在sqlplus中执行oracle的脚本CreateMyTables和CreateMyViews。
          #3. 最后执行sqlplus的退出命令,退出sqlplus。自动化工作完成。
          /> sqlplus "/as sysdba" <<-SQL
          > @CreateMyTables
          > @CreateMyViews
          > exit
          > SQL
            
    十一、获取进程的运行时长(单位: 分钟):

          在进程监控脚本中,我们通常需要根据脚本的参数来确定有哪些性能参数将被收集,当这些性能参数大于最高阈值或小于最低阈值时,监控脚本将根据实际的情况,采取预置的措施,如邮件通知、直接杀死进程等,这里我们给出的例子是收集进程运行时长性能参数。
          ps命令的etime值将给出每个进程的运行时长,其格式主要为以下三种:
          1. minutes:seconds,如20:30
          2. hours:minutes:seconds,如1:20:30
          3. days-hours:minute:seconds,如2-18:20:30
          该脚本将会同时处理这三种格式的时间信息,并最终转换为进程所流经的分钟数。
          /> cat > test11.sh
          #!/bin/sh
          #1. 通过ps命令获取所有进程的pid、etime和comm数据。
          #2. 再通过grep命令过滤,只获取init进程的数据记录,这里我们可以根据需要替换为自己想要监控的进程名。
          #3. 输出结果通常为:1 09:42:09 init
          pid_string=`ps -eo pid,etime,comm | grep "init" | grep -v grep`
          #3. 从这一条记录信息中抽取出etime数据,即第二列的值09:42:09,并赋值给exec_time变量。
          exec_time=`echo $pid_string | awk '{print $2}'`
          #4. 获取exec_time变量的时间组成部分的数量,这里是3个部分,即时:分:秒,是上述格式中的第二种。
          time_field_count=`echo $exec_time | awk -F: '{print NF}'`
          #5. 从exec_time变量中直接提取分钟数,即倒数第二列的数据(42)。
          count_of_minutes=`echo $exec_time | awk -F: '{print $(NF-1)}'`
        
          #6. 判断当前exec_time变量存储的时间数据是属于以上哪种格式。
          #7. 如果是第一种,那么天数和小时数均为0。
          #8. 如果是后两种之一,则需要继续判断到底是第一种还是第二种,如果是第二种,其小时部分将不存在横线(-)分隔符分隔天数和小时数,否则需要将这两个时间字段继续拆分,以获取具体的天数和小时数。对于第二种,天数为0.
          if [ $time_field_count -lt 3 ]; then
              count_of_hours=0
              count_of_days=0
          else
              count_of_hours=`echo $exec_time | awk -F: '{print $(NF-2)}'`
              fields=`echo $count_of_hours | awk -F- '{print NF}'`
              if [ $fields -ne 1 ]; then
                  count_of_days=`echo $count_of_hours | awk -F- '{print $1}'`
                  count_of_hours=`echo $count_of_hours | awk -F- '{print $2}'`
              else
                  count_of_days=0
              fi
          fi
          #9. 通过之前代码获取的各个字段值,计算出该进程实际所流经的分钟数。
          #10. bc命令是计算器命令,可以将echo输出的数学表达式计算为最终的数字值。
          elapsed_minutes=`echo "$count_of_days*1440+$count_of_hours*60+$count_of_minutes" | bc`
          echo "The elapsed minutes of init process is" $elapsed_minutes "minutes."
          CTRL+D
          /> ./test11.sh

          The elapsed minutes of init process is 577 minutes.
       
    十二、模拟简单的top命令:
        
          这里用脚本实现了一个极为简单的top命令。为了演示方便,我们在脚本中将很多参数都写成硬代码,你可以根据需要更换这些参数,或者用更为灵活的方式替换现有的实现。
          /> cat > test12.sh
          #!/bin/sh
          #1. 将ps命令的title赋值给一个变量,这样在每次输出时,直接打印该变量即可。
          header=`ps aux | head -n 1`
          #2. 这里是一个无限循环,等价于while true
          #3. 每次循环先清屏,之后打印uptime命令的输出。
          #4. 输出ps的title。
          #5. 这里需要用sed命令删除ps的title行,以避免其参与sort命令的排序。
          #6. sort先基于CPU%倒排,再基于owner排序,最后基于pid排序,最后再将结果输出给head命令,仅显示前20行的数据。
          #7. 每次等待5秒后刷新一次。
         while :
          do
              clear
              uptime
              echo "$header"
              ps aux | sed -e 1d | sort -k3nr -k1,1 -k2n | head -n 20
              sleep 5
          done
          CTRL+D    
          /> ./test12.sh
          21:55:07 up 13:42,  2 users,  load average: 0.00, 0.00, 0.00
          USER       PID %CPU %MEM    VSZ   RSS   TTY      STAT START   TIME   COMMAND
          root      6408     2.0      0.0   4740   932   pts/2    R+    21:45     0:00   ps aux
          root      1755     0.2      2.0  96976 21260   ?        S      08:14     2:08   nautilus
          68        1195     0.0      0.4   6940   4416    ?        Ss    08:13     0:00   hald
          postfix   1399    0.0      0.2  10312  2120    ?        S      08:13     0:00   qmgr -l -t fifo -u
          postfix   6021    0.0      0.2  10244  2080    ?        S      21:33     0:00   pickup -l -t fifo -u
          root         1       0.0      0.1   2828   1364    ?        Ss     08:12    0:02   /sbin/init
          ... ...

  • 相关阅读:
    LeetCode 811. Subdomain Visit Count (子域名访问计数)
    LeetCode 884. Uncommon Words from Two Sentences (两句话中的不常见单词)
    LeetCode 939. Minimum Area Rectangle (最小面积矩形)
    LeetCode 781. Rabbits in Forest (森林中的兔子)
    LeetCode 739. Daily Temperatures (每日温度)
    三种方式实现按钮的点击事件
    239. Sliding Window Maximum
    14.TCP的坚持定时器和保活定时器
    13.TCP的超时与重传
    12.TCP的成块数据流
  • 原文地址:https://www.cnblogs.com/orangeform/p/2272814.html
Copyright © 2020-2023  润新知