Shell脚本编程

Shell历史

  • shell:命令解释器,作用是解释执行用户的命令
  • 执行命令方式:
    • 交互式:用户输入一条命令,Shell就解释执行一条
    • 批处理:用户事先编写好一个脚本,里面包含多条命令,让Shell一次性执行完这些命令
  • 版本(仅介绍3种):
    • 1、sh(Bourne Shell):最原始版本shell
    • 2、bash(Bourne Again Shell):增强版shell,是各种Linux发行版标准配置的Shell
    • 3、zsh: 命令补全功能非常强大,可以补齐路径,补齐命令,补齐参数等
  • 内建命令:
    • cd、echo、alias、umask、exit等命令即是内建命令,凡是用which命令查不到程序文件所在位置的命令都是内建命令。
    • 内建命令没有单独的man手册,要在man手册中查看内建命令,应该
      man bash-builtins
    • 用户在命令行输入命令后,一般情况下Shell会fork一个子进程并exec该命令,然后父进程切换到后台并等待回收子进程。但是Shell的内建命令例外,并不创建新的进程,但执行结束后也会有一个状态码(Exit Status),0表示成功,非0表示失败,可以使用特殊变量$?读取,比如:
      ls
      echo $?

执行脚本

  • 编写一个简单的脚本test.sh:
#! /bin/sh

pwd
cd ..
pwd
    • 第一行开头#!(称为Shebang),它表示该脚本使用后面指定的解释器/bin/sh解释执行。给这个脚本文件加上可执行权限(chmod a+x test.sh)后,可通过 ./test.sh执行,Shell会fork一个子进程并调用exec执行./test.sh这个程序。
    • 也可以通过/bin/sh test.sh去执行,这种方式不需要test.sh文件具有可执行权限
  • 如果将命令行下命令用()括号括起来,也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如:
    (pwd;cd..;pwd)

    和上面shell脚本的执行结果相同,不会改变cd ..命令改变的是子Shell的PWD,而不会影响到交互式Shell

  • 下面3种执行方式不会创建子Shell,而是直接在交互式Shell下执行,会改变交互式Shell的PWD:
    • pwd;cd..;pwd
    • source ./test.sh
    • . ./test.sh

      .和source的执行效果一样,注意.和后面有一个空格

基本语法

  • 变量
    • 按照惯例,Shell变量由全大写字母加下划线组成,有两种类型的Shell变量:
      • 环境变量
        • 环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。
        • env和printenv命令可以显示当前Shell进程的环境变量。
      • 本地变量
        • 只存在于当前Shell进程
        • set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数
    • 定义和赋值本地变量:
      VARNAME=value
      

        注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。

    • 定义和导出环境变量---export:
      export VARNAME=value

      也可以分两步完成:

      VARNAME=value
      export VARNAME
    • 删除变量
      unset VARNAME

      unset命令可以删除已定义的环境变量或本地变量

    • 获取变量值
      • 用${VARNAME}可以表示它的值,在不引起歧义的情况下也可以用$VARNAME表示它的值
      • 比如:
        echo $SHELL

        注意变量的值均为字符串类型,如果对一个没有定义的变量取值,则值为空

  • 文件名代换:
    • 通配符:*、?、[]
      • *:匹配0个或多个任意字符
      • ?:匹配一个任意字符
      • [] :匹配方括号中任意一个字符的一次出现
  • 命令代换:``或$()
    • ``或$()也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中,例如:
      DATE=`date`
      #DATE=$(date)
      echo $DATE
  • 算术代换:$(())
    • 用于算术计算,$(())中的Shell变量取值将转换成整数,$(())中只能用+-*/和()运算符,并且只能做整数运算:
      VAR=10
      echo $VAR+3
      #结果:10+3
      echo $(($VAR+3))
      #结果:13
  • 转义字符\
    • \在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。例如:
      echo $SHELL
      #/bin/bash
      echo \$SHELL
      #$SHELL
      touch \$\ \$
      #创建文件名'$ $'的文件
    • \还有一种用法,在\后敲回车表示续行
  • 单引号和双引号
    • Shell脚本中的单引号和双引号一样都是字符串的界定符
    • 单引号用于保持引号内所有字符的字面值
    • 双引号中如果有变量,允许变量扩展,与单引号不同
      DATE=$(date)
      echo '$DATE'
      # $DATE
      echo "$DATE"
      #Mon Jul 26 18:02:59 CST 2021
      echo "$DATE+10000"
      #Mon Jul 26 18:02:59 CST 2021+10000

shell脚本语法

  • 条件测试:test或[
    • 命令test或[可以测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1
    • 左方括号[确实是一个命令的名字,传给命令的各参数之间应该用空格隔开。命令test或[的参数形式是相同的,只不过test命令不需要]参数。以[命令为例,常见的测试命令如下表所示:
      [ -d DIR ]              如果DIR存在并且是一个目录则为真
      [ -f FILE ]             如果FILE存在且是一个普通文件则为真
      [ -z STRING ]           如果STRING的长度为零则为真
      [ -n STRING ]           如果STRING的长度非零则为真
      [ STRING1 = STRING2 ]   如果两个字符串相同则为真
      [ STRING1 != STRING2 ]  如果字符串不相同则为真
      [ ARG1 OP ARG2 ]        ARG1和ARG2应该是取值为整数的变量,OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之一
      [ ! EXPR ]              EXPR可以是上表中的任意一种测试条件,!表示逻辑反
      [ EXPR1 -a EXPR2 ]      EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示逻辑与
      [ EXPR1 -o EXPR2 ]      EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示逻辑或

      注意:作为一种好的Shell编程习惯,应该总是把变量取值放在双引号之中。如果变量事先没有定义,则被Shell展开为空字符串,会造成测试条件的语法错误

    • 示例:
      python@Elite-Wang:~$ VAR=''
      python@Elite-Wang:~$ [ -z "$VAR" ]
      python@Elite-Wang:~$ echo $?
      0
      python@Elite-Wang:~$ unset VAR
      python@Elite-Wang:~$ VAR1=1
      python@Elite-Wang:~$ VAR2=2
      python@Elite-Wang:~$ test "$VAR1" -lt "$VAR2"
      python@Elite-Wang:~$ echo $?
      0
      python@Elite-Wang:~$ VAR=abc
      python@Elite-Wang:~$ [ -d downloads -a $VAR = 'abc' ]
      python@Elite-Wang:~$ echo $?
      0
      python@Elite-Wang:~$ unset VAR
      python@Elite-Wang:~$ [ -d downloads -a $VAR = 'abc' ]
      -bash: [: too many arguments
      python@Elite-Wang:~$ [ -d downloads -a "$VAR" = 'abc' ]
      python@Elite-Wang:~$ echo $?
      1
  • if/then/elif/else/fi
    • 在Shell中用if、then、elif、else、fi这几条命令实现分支控制,例如:
      #! /bin/sh
      
      if [ -f ~/.bashrc ]; then
          . ~/.bashrc
      fi

      其实是三条命令,if [ -f ~/.bashrc ]是第一条,then . ~/.bashrc是第二条,fi是第三条。如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号了,另外,then后面有换行,但这条命令没写完,Shell会自动续行,把下一行接在then后面当作一条命令处理。和[命令一样,要注意命令和各参数之间必须用空格隔开。if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令,如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。if后面的子命令通常是测试命令,但也可以是其它命令。Shell脚本没有{}括号,所以用fi表示if语句块的结束。

    • :(冒号):是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。示例:
      #! /bin/bash
      
      if [ -f /bin/bash ]; then
          echo '/bin/bash is a file'
      else
          echo '/bin/bash is not a file'
      fi  
      if :; then
          echo 'always true'
      fi  
    • read命令:作用是等待用户输入一行字符串,将该字符串存到一个Shell变量中,示例:
      #! /bin/sh
      
      echo "Is it morning? Please answer yes or no."
      read YES_OR_NO
      if [ "$YES_OR_NO" = "yes" ]; then
        echo "Good morning!"
      elif [ "$YES_OR_NO" = "no" ]; then
        echo "Good afternoon!"
      else
        echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
      fi
  •  case/esac
    • Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,esac表示case语句块的结束。例如:
      #! /bin/sh
      
      echo "Is it morning? Please answer yes or no."
      read YES_OR_NO
      case "$YES_OR_NO" in
      yes|y|Yes|YES)
        echo "Good Morning!";;
      [nN]*)
        echo "Good Afternoon!";;
      *)
        echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
        exit 1;;
      esac
      exit 0
    • 使用case语句的例子可以在系统服务的脚本目录/etc/init.d中找到。这个目录下的脚本大多具有这种形式(以/etc/init.d/nfs-kernel-server为例):
      case "$1" in
          start)
              ...
          ;;
          stop)
              ...
          ;;
          reload | force-reload)
              ...
          ;;
          restart)
          ...
          *)
              log_success_msg "Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}"
              exit 1
          ;;
      esac

      $1是一个特殊变量,在执行脚本时自动取值为第一个命令行参数

  • for/do/done
    • for循环,示例:
      #! /bin/sh
      
      for FRUIT in apple banana pear; do
        echo "I like $FRUIT"
      done
      FRUIT是一个循环变量,第一次循环$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear
    • 在命令行中可以写成一行,命令间用;隔开:
      for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done
  • while/do/done
    • 比如一个验证密码的脚本:
      #! /bin/sh
      
      echo "Enter password:"
      read TRY
      while [ "$TRY" != "secret" ]; do
        echo "Sorry, try again"
        read TRY
      done
    • 控制循环次数
      #! /bin/sh
      
      COUNTER=1
      while [ "$COUNTER" -lt 10 ]; do
        echo "Here we go again"
        COUNTER=$(($COUNTER+1))
      done
  • break和continue
    • break跳出整个循环,continue跳过本次循环
    • 用户输错五次密码就报错退出:
      #! /bin/sh
      
      echo 'please input password:'
      COUNTER=0
      while :; do
          read PASSWD
          if [ "$PASSWD" = "123456" ]; then
              echo "login success!"
              break
          else
              COUNTER=$(($COUNTER+1))
              if [ "$COUNTER" -eq 5 ]; then
                  echo '5 chances are used up, login fails!'
                  break
              else
                  echo 'password error,please login again!'
              fi
          fi
  • 位置参数和特殊变量
    • 常用的位置参数和特殊变量
      $0  相当于C语言main函数的argv[0]
      $1、$2...    这些称为位置参数(Positional Parameter)
      $#  表示参数的个数,注意这里的#后面不表示注释
      $@  表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。
      $*  表示参数列表"$1" "$2" ...,同上
      $?  上一条命令的Exit Status
      $$  当前进程号
    • shift命令可以左移位置参数,比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。例如:
      #! /bin/sh
      
      echo "$#"
      echo "$@"
      shift 2
      echo "$*"
      shift
      echo "$*"
      echo "$?"
      echo "$$"

      运行结果:

      python@Elite-Wang:~$ ./test.sh aa bb cc dd ee ff
      6
      aa bb cc dd ee ff
      cc dd ee ff
      dd ee ff
      0
      22248

Shell输入输出

  • echo
    • echo显示文本行或变量,或者把字符串输入到文件。
    • 参数:
      • -e 解析转义字符
      • -n 不回车换行。默认情况echo回显的内容后面跟一个回车换行。
      • 示例:
        python@Elite-Wang:~$ echo "hello world\n\n"
        hello world\n\n
        python@Elite-Wang:~$ echo -e "hello world\n\n"
        hello world
        
        
        python@Elite-Wang:~$ echo -n "hello world\n\n"
        hello world\n\npython@Elite-Wang:~$ 
  • 管道 |
    • 可以通过管道把一个命令的输出传递给另一个命令做输入。
      ls -ahl | more
      ps -aux | grep 'redis'
  • tee
    • tee命令把结果输出到标准输出,另一个副本输出到相应文件。
      ls -aux | tee test.txt
  • 文件重定向
    cmd > file             把标准输出重定向到新文件中
    cmd >> file            追加
    cmd > file 2>&1        标准出错也重定向到1所指向的file里
    cmd >> file 2>&1
    cmd < file1 > file2    输入输出都定向到文件里
    cmd < &fd              把文件描述符fd作为标准输入
    cmd > &fd              把文件描述符fd作为标准输出
    cmd < &-               关闭标准输入

    文件描述符0表示标准输入,1标准输出,2标准错误

  • 函数
    • 函数定义中没有参数列表,例如:
      #! /bin/sh
      
      foo(){ echo "Function foo is called";}
      echo "-=start=-"
      foo
      echo "-=end=-"

      注意函数体的左花括号'{'和后面的命令之间必须有空格或换行,如果将最后一条命令和右花括号'}'写在同一行,命令末尾必须有;号。

    • Shell函数没有参数列表并不表示不能传参数,调用函数时可以传任意个参数,在函数内同样是用$0、$1、$2等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的$0、$1、$2等变量。函数中可以用return命令返回,如果return后面跟一个数字则表示函数的Exit Status。
    • 通过脚本创建多个目录,各目录名通过命令行参数传入,示例:
      #! /bin/sh
      
      is_directory()
      {
          if [ ! -d "$1" ]; then
              return 1
          else
              return 0
          fi
      }
      for DIR in "$@"; do
          if is_directory "$DIR"; then
              echo "$DIR exists"
              :
          else
              echo "create directory $DIR"
              mkdir "$DIR" > /dev/null 2>&1
              if [ $? -ne 0 ]; then
                  echo "create directory $DIR fail"
              fi
          fi
      done

      注意is_directory()返回0表示真返回1表示假。

Shell脚本调试

  • Shell提供了一些用于调试脚本的选项,如下所示:
    • -n:读一遍脚本中的命令但不执行,用于检查脚本中的语法错误
    • -v:一边执行脚本,一边将执行过的脚本命令打印到标准错误输出
    • -x:提供跟踪执行信息,将执行的每一条命令和结果依次打印出来
  • 使用这些选项有三种方法:
    • 一是在命令行提供参数
      sh -x ./test.sh
    • 二是在脚本开头提供参数
      #! /bin/sh -x
    • 三是在脚本中用set命令启用或禁用参数
      #! /bin/sh
      if [ -z "$1" ]; then
        set -x
        echo "ERROR: Insufficient Args."
        exit 1
        set +x
      fi

      set -x和set +x分别表示启用和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。

 Shell常用工具

  • grep
    • Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。
    • grep正则表达式的Basic规范中字符?+{}|()应解释为普通字符,要表示上述特殊含义则需要加\转义。如果用egrep,或者grep加上-E参数,则不需要转义
    • 主要参数:
      grep --help
      
      [options]主要参数:
      -c:只输出匹配行的计数。
      -i:不区分大小写。
      -h:查询多文件时不显示文件名。
      -l:查询多文件时只输出包含匹配字符的文件名。
      -n:显示匹配行及 行号。
      -s:不显示不存在或无匹配文本的错误信息。
      -v:显示不包含匹配文本的所有行。
      --color=auto :可以将找到的关键词部分加上颜色的显示。
    • pattern正则表达式主要参数:
      \: 忽略正则表达式中特殊字符的原有含义。
      ^:匹配正则表达式的开始行。
      $: 匹配正则表达式的结束行。
      \<:从匹配正则表达式的行开始。
      \>:到匹配正则表达式的行结束。
      [ ]:单个字符,如[A]即A符合要求 。
      [ - ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。
      .:所有的单个字符。
      *:匹配前面字符的个数,长度可以为0或多个。
    • grep命令使用简单实例
      $ grep 'test' d*
      显示所有以d开头的文件中包含 test的行。
      
      $ grep 'test' aa bb cc
      显示在aa,bb,cc文件中匹配test的行。
      
      $ grep '[a-z]\{5\}' aa
      显示所有包含每个字符串至少有5个连续小写字符的字符串的行。
      
      $ grep 'w\(es\)t.*\1' aa
      如果west被匹配,则es就被存储到内存中,并标记为1,然后搜索任意个字符(.*),这些字符后面紧跟着 另外一个es(\1),找到就显示该行。如果用egrep或grep -E,就不用”\”号进行转义,直接写成’w(es)t.*\1′就可以了。
    • grep命令使用复杂实例
      • 明确要求搜索子目录
        grep -r
      • 忽略子目录
        grep -d skip
      • 用于搜索的特殊符号
        \< 和 \> 分别标注单词的开始与结尾。
        例如:
        grep man * 会匹配 ‘Batman’、’manic’、’man’等
        grep ‘\<man’ * 匹配’manic’和’man’,但不是’Batman’
        grep ‘\<man\>’ 只匹配’man’,而不是’Batman’或’manic’等其他的字符串
        ‘^’:指匹配的字符串在行首
        ‘$’:指匹配的字符串在行 尾
  • find
    • 命令一般形式
      find pathname -options [-print -exec -ok ...]
    • 命令的参数
      pathname: find命令所查找的目录路径。例如用.来表示当前目录,用/来表示系统根目录,递归查找。
      -print: find命令将匹配的文件输出到标准输出。
      -exec: find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为'command' {  } \;,注意{   }和\;之间的空格。
      -ok: 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。
    • 命令选项
      -name   按照文件名查找文件。
      -perm   按照文件权限来查找文件。
      -prune  使用这一选项可以使find命令不在当前指定的目录中查找,如果同时使用-depth选项,那么-prune将被find命令忽略。
      -user   按照文件属主来查找文件。
      -group  按照文件所属的组来查找文件。
      -mtime -n +n 按照文件的更改时间来查找文件,-n表示文件更改时间距现在n天以内,+n表示文件更改时间距现在n天以前。find命令还有-atime和-ctime 选项,但它们都和-m time选项。
      -nogroup 查找无有效所属组的文件,即该文件所属的组在/etc/groups中不存在。
      -nouser 查找无有效属主的文件,即该文件的属主在/etc/passwd中不存在。
      -newer file1 ! file2 查找更改时间比文件file1新但比文件file2旧的文件。
      -type   查找某一类型的文件,诸如:
          b - 块设备文件。
          d - 目录。
          c - 字符设备文件。
          p - 管道文件。
          l - 符号链接文件。
          f - 普通文件。
      -size n:[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。
      -depth   在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找。
      -fstype  查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件/etc/fstab中找到,该配置文件中包含了本系统中有关文件系统的信息。
      -mount   在查找文件时不跨越文件系统mount点。
      -follow  如果find命令遇到符号链接文件,就跟踪至链接所指向的文件。
    • 注意以下三个的区别:
      访问:
      -amin n 查找系统中最后N分钟访问的文件 -atime n 查找系统中最后n*24小时访问的文件
      改变文件状态:
      -cmin n 查找系统中最后N分钟被改变文件状态的文件 -ctime n 查找系统中最后n*24小时被改变文件状态的文件
      改变文件数据:
      -mmin n 查找系统中最后N分钟被改变文件数据的文件 -mtime n 查找系统中最后n*24小时被改变文件数据的文件
    • 使用exec或ok来执行shell命令
      • -exec选项后面跟随着所要执行的命令或脚本,然后是一对儿{},一个空格和一个\,最后是一个分号。
        find . -type f -exec ls -l {} \;
      • -ok是-exec选项的安全模式。它将在对每个匹配到的文件进行操作之前提示你。
        $ find . -name "*.conf"  -mtime +5 -ok rm {  } \;
        < rm ... ./conf/httpd.conf > ? n 
  • xargs
    • 在使用find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现 溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。
    • find命令把匹配到的文件传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一批,并如此继续下去。并且,使用xargs命令只有一个进程
    • xargs前面和管道 | 联用,后面跟上操作,相当于-exec 操作
      find . -perm -7 -print | xargs chmod o-w   #回收其它用户的写权限
      find . -type f -print | xargs grep "hello"  #用grep命令在所有的普通文件中搜索hello这个词
    • 利用xargs批量删除redis数据库中的键
      redis-cli keys "key*" | xargs redis-cli del

      批量删除redis数据库中以key开头的键

    • xargs加上'-i'参数后,可以用'{}'代替'|'前面的标准输出,批量设置以"key"开头key的过期时间,可以在终端中可以执行以下命令:
      redis-cli keys "key*" | xargs -i redis-cli expire {} 过期时间(单位:秒)
  • sed
    • 行处理工具
    • sed意为流编辑器(Stream Editor),在Shell脚本和Makefile中作为过滤器使用非常普遍,也就是把前一个程序的输出引入sed的输入,经过一系列编辑命令转换为另一种格式输出。
    • sed命令行基本格式:
      sed option 'script' file1 file2 ...
      sed option -f scriptfile file1 file2 ...
    • 选项含义
      --version            显示sed版本。
      --help               显示帮助文档。
      -n,--quiet,--silent  静默输出,默认情况下,sed程序在所有的脚本指令执行完毕后,将自动打印模式空间中的内容,这些选项可以屏蔽自动打印。
      -e script            允许多个脚本指令被执行。
      -f script-file, 
      --file=script-file   从文件中读取脚本指令,对编写自动脚本程序来说很棒!
      -i,--in-place        直接修改源文件,经过脚本指令处理后的内容将被输出至源文件(源文件被修改)慎用!可以使用tee命令重定向到文件中
      -l N, --line-length=N 该选项指定l指令可以输出的行长度,l指令用于输出非打印字符。
      --posix             禁用GNU sed扩展功能。
      -r, --regexp-extended  在脚本指令中使用扩展正则表达式
      -s, --separate      默认情况下,sed将把命令行指定的多个文件名作为一个长的连续的输入流。而GNU sed则允许把他们当作单独的文件,这样如正则表达式则不进行跨文件匹配。
      -u, --unbuffered    最低限度的缓存输入与输出。
    • 以上仅是sed程序本身的选项功能说明,至于具体的脚本指令,这里就简单介绍几个脚本指令操作作为sed程序的例子。
      a,append        追加
      i,insert        插入
      d,delete        删除
      s,substitution  替换
    • sed命令的格式:
      /pattern/action

      其中pattern是正则表达式,action是编辑操作。sed程序一行一行读出待处理文件,如果某一行与pattern匹配,则执行相应的action,如果一条命令没有pattern而只有action,这个action将作用于待处理文件的每一行。

    • 常用的sed命令:
      /pattern/p  打印匹配pattern的行
      /pattern/d  删除匹配pattern的行
      /pattern/s/pattern1/pattern2/   查找符合pattern的行,将该行第一个匹配pattern1的字符串替换为pattern2
      /pattern/s/pattern1/pattern2/g  查找符合pattern的行,将该行所有匹配pattern1的字符串替换为pattern2

      使用p命令需要注意,sed是把待处理文件的内容连同处理结果一起输出到标准输出的,因此p命令表示除了把文件内容打印出来之外还额外打印一遍匹配pattern的行。比如一个文件testfile的内容是

      123
      abc
      456

      打印其中包含abc的行

      sed '/abc/p' testfile
      
      123
      abc
      abc
      456

      要想只输出处理结果,应加上-n选项,使用d命令就不需要-n参数了,注意,sed命令不会修改原文件,删除命令只表示某些行不打印输出,而不是从原文件中删去。

    • 使用查找替换命令时,可以把匹配pattern1的字符串复制到pattern2中,比如:
      $ sed 's/bc/-&-/' testfile
      123
      a-bc-
      456
      pattern2中的&表示原文件的当前行中与pattern1相匹配的字符串
    • 再比如:
      $ sed 's/\([0-9]\)\([0-9]\)/-\1-~\2~/' testfile
      -1-~2~3
      abc
      -4-~5~6

      pattern2中的\1表示与pattern1的第一个()括号相匹配的内容,\2表示与pattern1的第二个()括号相匹配的内容。sed默认使用Basic正则表达式规范,如果指定了-r选项则使用Extended规范,那么()括号就不必转义了。

    • 执行多个脚本指令:
      $ sed  's/yes/no/;s/static/dhcp/'  ./testfile
      注:使用分号隔开指令。
      
      $ sed -e 's/yes/no/' -e 's/static/dhcp/' testfile
      注:使用-e选项。
  • awk
    • sed以行为单位处理文件,awk比sed强的地方在于不仅能以行为单位还能以列为单位处理文件。awk缺省的行分隔符是换行,缺省的列分隔符是连续的空格和Tab,但是行分隔符和列分隔符都可以自定义,比如/etc/passwd文件的每一行有若干个字段,字段之间以:分隔,就可以重新定义awk的列分隔符为:并以列为单位处理这个文件。awk实际上是一门很复杂的脚本语言,还有像C语言一样的分支和循环结构,但是基本用法和sed类似,awk命令行的基本形式为:
      awk option 'script' file1 file2 ...
      awk option -f scriptfile file1 file2 ...
    • 和sed一样,awk处理的文件既可以由标准输入重定向得到,也可以当命令行参数传入,编辑命令可以直接当命令行参数传入,也可以用-f参数指定一个脚本文件,编辑命令的格式为:
      /pattern/{actions}
      condition{actions}
    • 和sed类似,pattern是正则表达式,actions是一系列操作。awk程序一行一行读出待处理文件,如果某一行与pattern匹配,或者满足condition条件,则执行相应的actions,如果一条awk命令只有actions部分,则actions作用于待处理文件的每一行。比如文件testfile的内容表示某商店的库存量:
      ProductA  30
      ProductB  76
      ProductC  55

      打印每一行的第二列:

      $ awk '{print $2;}' testfile
      30
      76
      55
    • 自动变量$1、$2分别表示第一列、第二列等,类似于Shell脚本的位置参数,而$0表示整个当前行。再比如,如果某种产品的库存量低于75则在行末标注需要订货:
      $ awk '$2<75 {printf "%s\t%s\n", $0, "REORDER";} $2>=75 {print $0;}' testfile
      ProductA  30    REORDER
      ProductB  76
      ProductC  55    REORDER

      可见awk也有和C语言非常相似的printf函数。awk命令的condition部分还可以是两个特殊的condition-BEGIN和END,对于每个待处理文件,BEGIN后面的actions在处理整个文件之前执行一次,END后面的actions在整个文件处理完之后执行一次。

    • awk命令可以像C语言一样使用变量(但不需要定义变量),比如统计一个文件中的空行数
      awk '/^ *$/ {x=x+1;} END {print x;}' testfile
    • 有些awk变量是预定义的有特殊含义的:
      • awk常用的内建变量
        FILENAME  当前输入文件的文件名,该变量是只读的
        NR  当前行的行号,该变量是只读的,R代表record
        NF  当前行所拥有的列数,该变量是只读的,F代表field
        OFS 输出格式的列分隔符,缺省是空格
        FS  输入文件的列分融符,缺省是连续的空格和Tab
        ORS 输出格式的行分隔符,缺省是换行符
        RS  输入文件的行分隔符,缺省是换行符

        例如打印系统中的用户帐号列表:

        awk 'BEGIN {FS=":"} {print $1;}' /etc/passwd

         

posted @ 2021-08-04 17:38  eliwang  阅读(251)  评论(0编辑  收藏  举报