Linux系列11-Shell编程

  • 正则表达式
  • Shell编程
  • Shell定制
    如要下载笔记和代码请到我的github

    正则表达式

  • 正则表达式广泛应用在各种脚本语言中(解释型语言)包括Perl、PHP、Ruby和Python等,Linux的各种编程工具中也大量采用了正则表达式(Shell脚本编程)。
  • “正则表达式”(regexps)又被称作“模式”(至少在Linux中是这样),是被用来字符处理的一套规则;
    • 简而言之,正则表达式是一组对正在查找的文本的描述。
  • 目前在GNU/Linux中有两套库可用于正则表达式编程:
    • POSIX库:是Linux自带的正则表达式库(Python中也使用这套标准);
    • PCRE库:是Perl的正则表达式库。
  • 举个例子:匹配/usr/share/dict/words中“a开头、t结尾”的单词,采用命令如下:

    1
    2
    3
    $ egrep "^a.*t$" /usr/share/dict/words # egrep比grep支持更多正则表达式规则,此处grep也可以
    # 如果我们要统计总的单词数
    $ egrep '^a.*t$' /usr/share/dict/words | wc -c
  • 正则表达式字符集:

    • .:匹配换行符之外的任意一个字符,.能匹配的字符范围是最大的;
    • []:指定一个字符集,要求只能匹配其中的一个字符,如[abc]表示三选一;
    • -:连字符,和[]配合使用,如[a-zA-Z]表示匹配一个字母;
    • \<\>:一对分隔符,表示匹配一个单词,注意正则表达式中对单词的定义指的是两侧由非单词字符分隔的字符串(非单词字符指的是字母、数字、下划线以外的任何字符);
  • 字符类:POSIX风格的正则表达式还提供预定义字符类来匹配某些特定的字符(但是很多类都可以用简单的正则表达式表示);
匹配字符
[[:alnum:]] 字母、数字字符
[[:alpha:]] 字母字符
[[:lower:]] 小写字母
[[:upper:]] 大写字母
[[:digit:]] 小数
[[:xdigit:]] 十六进制数字
[[:punct:]] 标点符号
[[:blank:]] 制表符和空格
[[:space:]] 空格
[[:cntrl:]] 所有控制符
[[:print:]] 所有可打印字符
[[:graph:]] 除空格外所有可打印字符
  • 位置匹配:
    • ^:用于匹配行首;
    • $:用于匹配行尾。
    • 所以如果要匹配一个空行就用^$,两者不必非要一起使用。
  • 字符转义:
    • \:当要匹配特殊字符本身时,就在前加上转义字符,取消元字符本身的特殊含义。
  • 重复:
    • *:表示在*前面的模式应该重复0次或多次;
    • +:表示模式重复1次或多次;
    • ?:重复0次或1次;
    • {n}:模式重复n次;
    • {n,}:模式重复n次或更多;
    • {n,m}:模式不少于n次,不多于m次。
    • 一个例子,匹配不少于8位的数\<[1-9][0-9]{7,}\>
  • 子表达式,i.e.”分组”:
    • ():将部分字符组成一个字符组作为一个子模式,如”(or){2,}”表示整个子字符组重复2次以上,否则只重复紧挨的字符r。
  • 反义(^在行首表示位置匹配,放括号内表示反义):
    • ^:除了此字符以外,全部都可以,和[]配合使用,表示除括号内字符的任意一个其他字符。如[^ya]表示除y和a以外的任一字符。
  • 分支,或者叫“或”逻辑(一般正则表达式总是执行“与”逻辑,指同时满足给出的若干条件):
    • |:如^h|h$表示h开头”或”h结尾,而^hh$表示h开头”且”h结尾(即’hh’),一个表达式中可以出现多个或逻辑Jan(uary| |\.)
  • 逆向引用:子表达式中捕获的内容可以在正则表达式的其他地方再次使用;
    • \n:’反斜杠+编号n’表示第n组子表示匹配到的内容,如(\<*.\>).?( )*\1可以匹配如’cart cart’,’ha!ha’这些字符组;
    • 从左到右,第i个出现的子表达式编号为i。

Shell脚本编程

  • Shell本身就是一个命令解释器(而不是一门编程语言),所以可以在shell中运行命令(执行程序,命令就是语句的组合);
  • Shell本身都是同一个程序/bin/bash,子/父shell是指他们之间的调用关系。

    hello world

  • Shell因为分许多种类,严格说这里学习的应该BASH编程。
  • Shell脚本语言不需要编译(并不能说’脚本语言一定不需要编译/解释型语言不需要编译’)。
  • 下面一个例子,告诉我们:
    • 第一行总是以#!开头,指定脚本的运行环境,#! /bin/bash告诉shell运行此脚本应该使用的shell(可以省略但不是一个好习惯);
    • #表示注释;
    • echo执行时会自动加上一个换行符;
    • 文本加上可执行权限之后才能变成脚本执行,chmod a+x hello/chmod +x hello
      1
      2
      3
      4
      #! /bin/bash
      #Display a line

      echo 'Hello world!' #echo会在最后自动加上一个换行符

变量和运算符

  • 变量从诞生、消亡和作用范围:
    • Shell中变量使用之前不用事先声明(脚本语言貌似都如此);
    • =:赋值符号,如var=1就是给var赋值,注意’=’两边不能有空格;
    • $:shell编程中用于对一个变量进行解析,表示取得变量的值;
    • source/.:一般变量只在其所在的”脚本”中有效,shell中不可见其值,但source命令可以让一个脚本影响其父Shell环境(当前执行脚本的shell及其父shell),使值在shell中可见;
    • export:可以让脚本影响其子shell环境(子shell是在执行脚本中sh命令调用的shell);
    • 在配置脚本(父shell)中命令要影响shell用export,在shell中要影响配置脚本用source;
    • unset:手动注销一个变量。
  • 变量替换:

    • \:转义字符,如果要输出特殊字符时使用(类所有编程语言),如\$输出美元符号;
    • {}:限定变量的开始和结束(类python),如:
      1
      2
      3
      #! /bin/bash
      word="big"
      echo "The apple is ${word}."
  • 位置变量:依次序获取各命令行参数;

    • $0:表示第一个命令行参数,这个参数总是脚本的名字;
    • $n:表示第n个参数(不包括脚本名);
    • ${n}:当n为一位数以上时,需要用大括号将位置序号括起来;
    • $*/$@:参数列表(不包括脚本名);
    • $#:包含参数个数(参数列表长度)。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      #!/bin/bash
      # 是$@最常见的用法
      #! /bin/bash
      echo "$# files to list:"

      for file in $@
      do
      ls -l $file
      done
  • BASH引号规则

    • "":双引号,阻止Shell对大多特殊字符(如#)进行解释,但$/`/“仍保持其特殊含义;
    • '':单引号,阻止shell对所有字符进行解释;
    • \`:倒引号,当倒引号括起一个shell命令时,该命令将被执行,执行后的输出结果将作为表达式的值,倒引号中的特殊字符一般都被解释。
  • 运算符:shell完全复制了C语言中的运算符和优先级规则。日常只使用其中部分即可,数学运算并不是shell的强项。所有可用的运算符如下,优先级从高到低:
    • -,+:[单目[负 正][减 加]];
    • !,||,&&:逻辑[非 或 与];
    • ~,&,^,|,>>,<<:按位[取反 与 异或 或 右移 左移];
    • <=,>=,<,>,==,!=:[小于等于 大于等于 小于 大于 等于 不等于];
    • *,/,%:[乘 除 取模];
    • =,[+-*/%&^|(<<)(>>)]=:赋值,运算并赋值。
    • 同样可用()改变优先级;
  • shell中相等可以用===表示,因为=赋值时两边无空格(因为变量不是命令不能分开,而整个语句才能算是一个命令),而表示比较时两边有空格。

    表达式求值

  • $[]:求值后整体赋值。

    • [base#]n可以表示从2到36进制的任何一个n值(默认十进制),如2#10表示二进制数10;
    • $[]可以接受不同基数的数字求值;
    • 举个例子,如下第一段输出结果是1+2,因为shell是一种弱类型的语言,换言之shell不知道num的类型,因此只能简单取得$num将整个表达式赋值。如果按第二段就能计算后再输出结果。
      1
      2
      3
      4
      5
      6
      7
      #! /bin/bash
      num=1
      # ouput 1+2
      num=$num+2
      echo $num
      # output 3
      num=$[num]
  • expr命令也对表达式执行求值操作,可以允许更复杂的表达式。

  • let:此命令也指导shell进行表达式求值,功能类似$[],命令右边的表达式不能有空格,如let num=$num+1会输出值而非表达式。

    脚本执行命令和控制语句

  • if条件语句格式(每个if语句都必须用fi结尾):

    • 格式1:if-then-fi;

      1
      2
      3
      4
      if test-commands
      then
      commands
      fi
    • 格式2:if-then-elif-then-else-fi,当然各条件语句之间必须互斥;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      if test-command-1
      then
      commands-1
      elif test-command-2
      then
      commands-2
      elif test-command-3
      then
      commands-3
      else
      commands-4
      fi
      - **注意**(由例子):
      - (1)条件判断语句中`[`/`]`与表达式之间必须有空格,`=`两边必须有空格;
      - (2)条件语句后可以跟`;`也可以不跟,只是用于分隔语句;
      - (3)字符串可以用`""`包围,也可以不用,等号左边的表达式/前面有`$`的是变量,否则可以自动认为是字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! /bin/bash
# if例子
echo "Enter password:"
read password
echo $password

if [ "$password" = john ];then
echo "Hello John"
elif [ "$password" = mike ];
then
echo "Hello Mike"
elif [ "$password" = lewis ]
then
echo "Hello Lewis"
else
echo "I don't know you,go away"
fi
  • case多选结构:用于在一系列模式中匹配某个变量的值;

    • 基本语法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      case word in
      pattern1)
      commands1
      ;;
      pattern2)
      commands2
      ;;
      ...
      patternN)
      commandsN
      ;;
      esac
    • 注意(将if例子用case重写):

    • (1) ;;相当于C语言中的break,shell遇见时会跳转到case结构的最后,但是Shell中;;是不能省略的;
    • (2)case语句是逐条检索匹配的;
    • (3)case结构最后一个模式通常用*),因*用于匹配所有的字符串;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      #! /bin/bash
      # an example of case
      # note $n means nth input params
      # 重写if例子
      case $1 in
      john)
      echo "Hello John!"
      ;;
      mike)
      echo "Hello Mike!"
      ;;
      lewis)
      echo "Hello Lewis!"
      ;;
      *)
      echo "Go away!!"
      ;;
      esac

条件测试

  • if判断的依据是程序的返回值(0为真/正常,非0为假/出错序号):
    • if接受一个程序名作为参数,根据执行程序的返回值判断是否执行:
    • (1)如果返回值为0,表示True;
    • (2)如果返回值为1,表示False。
  • 例子如下,只有返回值为0的第2条if语句成功执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #!/bin/bash

    if ./testscript -1
    then
    echo "testscript exit -1"
    fi

    if ./testscript 0
    then
    echo "testscript exit 0"
    fi

    if ./testscript 1
    then
    echo "testscript exit 1"
    fi
    #----------testscript--------
    #!/bin/bash
    #表示退出并返回输入所有参数
    exit $@
  • test命令和空格的使用:

    • test:if语句既然只接受程序名为参数,所以if的条件判断需要引入一个特定的命令即test,也是[方括号的同义词;
    • 关于空格:
    • (1)test[都是/usr/bin下的命令,而判断的字符串(…)和=]都是要求输入的参数,参数之间必须要空格分开(!!在赋值语句中=两边一定没有空格);
    • (2)总的来说,空格在shell这个命令解释器中的作用就是分隔命令与参数/参数与参数。
  • test/[命令可以对以下3类表达式进行测试(可以在man test中看到详细内容):
    • 字符串比较(字符串相等/字符串是否为空):
      • 引号的使用:Bash中给字符串两边加""不是必要的,因为Bash会自动给没有值的变量加上引号(但是有些shell不如此),为保证清晰性和可移植性应为字符串变量加上引号。
选项 描述
-z str 当字符串str长度为0时返回真
-n str 当字符串长度不为0时返回真
str1 = str2 相等时返回真
str1 != str2 不等时返回真
  • 文件测试:用于判断一个文件是否满足特定的条件;
选项 描述
-d pathname 是目录时返回真
-e pathname 指定文件或目录存在时为真
-f file 是常规文件(非符号链接、管道、目录等)时为真
-h file 是符号链接文件时返回真
-[rwx] pathname 当指定的文件或目录设置了可[读 写 执行] 权限时为真
  • 数字比较:只能用于比较正/负整数test int1 option int2/[ int1 option int2 ];
选项 描述
-eq int1 == int2
-ne int1 != int2
-lt int1 < int2
-gt int1 > int2
-le int1 <= int2
-ge int1 >= int2
  • 复合表达式:用逻辑(与或非)串起的多个表达式;
    • 注意:Shell的内建条件操作符&&||可以代替下面的-a-o;前者连接两条[test语句,逻辑清晰;后者只用一条[/test语句,执行效率相对更高;如`[ -f $@ -a -x /usr/bin/vim ]`等于[ -f $@ ] && [ -x /usr/bin/vim ]
操作符 描述
!expr “非”运算
expr1 -a expr2 “与”运算
expr1 -o expr2 “或”运算

循环结构

  • Shell中的循环结构有3种:(1)while;(2)until;(3)for;
  • 条件测试时比较变量值时不能忘记$符号;
  • while语句:

    • 基本结构:

      1
      2
      3
      4
      while test-commands
      do
      commands
      done
    • while语句的测试条件除了使用test,[函数,还可以利用函数read等的返回值。

  • until语句:和while功能完全一样,但是测试条件相反。

    • 基本语法,直到条件成立才停下:
      1
      2
      3
      4
      until test-commands
      do
      commands
      done
  • for语句:从列表/值表中逐一读取值进行操作,直到取完所有的值;

    • 值表:一系列以空格分隔的值;
    • seq:此命令自动接受一个参数n,产生1到n(均包含)的值表;
    • 基本用法:
      1
      2
      3
      4
      for variable [in list]
      do
      commands
      done
  • read:读取用户输入;

    • 三种模式:
      • (1)接受一个变量名作为参数,从标准输入中接收到的信息存放在该变量中;
      • (2)不提供变量名,读取的信息将放在变量REPLY中;
      • (3)提供多个变量名作为参数,Bash默认空格、制表符和换行符为分隔符,将输入拆开分别赋值给各变量;
    • read常用来在输出一段内容后暂停,等待用户的下一步指令(如“继续”)。

      脚本执行命令

  • exit:强行退出一个脚本,并向调用脚本的父进程返回一个整数值;
    • 进程成功运行,返回值是0,非0值表示发生某种异常;
    • 简要用法是exit n
  • trap:用于捕捉一个信号,如进程通信中,用于捕捉且忽视一个信号;
    • trap置于文件首才能捕获信号;
    • 常见信号类型用kill -l获取(INT为’Ctrl+c’,EXIT为’Ctrl+D’);
    • 基本语法trap 'commands' SIGNAL
  • &&||用于创建命令表:命令表利用前一个命令的退出值来控制是否执行另一条命令,命令会被自动识别寻找参数输入。

    • 3种形式命令表(;顺序命令表已经在if等语句中出现过):
      | 表现形式 | 说明 |
      | — | — |
      | a&&b | “与”命令表。当且仅当a执行成功,才执行b |
      | a||b | “或”命令表。当且仅当a执行失败,才执行b |
      | a;b | 顺序命令表。先执行a,再执行b |
    • 关于命令的生效范围(见例子):在脚本中的程序通常是调用子shell完成功能,如果要在执行脚本时对当前shell(即父shell)完成,用source命令。
      1
      2
      3
      4
      5
      #!/bin/bash

      dirname=~/LearningNotes/git
      #echo $dirname
      test -d $dirname && cd $dirname
  • 其他Shell编程工具:

    • 以下命令均不改变源文件;
    • cut:从输入行中提取指定部分;
      • -c:提取一行中指定范围的字符,如cut -c3-6 file_name;
      • -f:提取指定行中指定的字段,分隔符用-d指定(否则默认使用TAB),如cut -d" " -f2 file_name截取第2个字段。
    • diff:用于确定两个版本的源文件存在哪些修改;
      • 用法:diff file1 file2
    • sort:以行为输入单位,对其按照字母顺序进行排列;
      • -r:默认按字母升序排列,-r使按降序排列;
      • -k<num>:默认按照第1个字段执行排序,-k可以指定按第字段排序。
    • uniq:从已排好序的输入行中删除重复的行,通常和sort命令结合管道工作。
    • tr:按照用户指定方式对字符执行替换,结果输送到标准输出;
      • tr "ABH" "HCA" < file:ABH分别替换为HCA;
      • tr "ABC" "[Z*]" < file/tr "A-C" "[Z*]" < file:替换成”Z”;
      • tr --delete " " < file:删除空格。
    • wc:用来统计文件中字节、单词和行的数量;
      • 常用选项:
选 项 描 述
-c/--bytes 显示字节数
-l/--lines 显示行数
-L/--max-line-length 显示最长一行的长度
-w/--words 显示单词个数
--help 显示帮助
  • substr:从字符串提取一部分,是shell内建的运算符,必须使用expr进行表达式求值;
    • 依次接受3个参数:(1)字符串/存放字符串的变量;(2)提取开始的位置(从1开始计数);(3)需要提取的字符数。
    • 用法:expr substr "Hello World" 1 5
  • seq:用于产生一个整数数列;
    • 只指定结束值:seq <end>产生从1到的数列;
    • 指定开始和结束:seq <start> <end>产生从的数列;
    • 指定开始结束和步长:seq <start> <step> <end>

      Shell定制

    • 设置环境变量
    • 设置别名

      修改环境变量

    • “环境变量”是一些和当前Shell有关的变量,用于定义特定的Shell行为;
    • printenv查看当前Shell的所有环境变量。
    • PATH:搜索路径,是最常用的环境变量之一,这个变量告诉Shell在什么地方查找用户要求执行的程序;\
  • PATH变量用一系列冒号分隔各目录,用户如果没有命令完整路径,Shell会依次在PATH变量指定的目录中查找;
  • 追加路径:PATH=$PATH:/usr/local/bin;
  • shell中修改的变量只在当前shell中有效(所以有配置文件的存在);
  • 执行本地程序用./program一是为了安全,在安全性要求更高的场合应该输入完全路径;二是因为命令通常在PATH中搜索,所以要加上./目录才能运行。
    • alias:设置命令(即程序)别名;
  • 用法:alias ll='ls -l'
    • 配置文件:shell中运行的命令只是临时有效,要保持长久有效需要将命令输入到配置文件中。
  • ~/.bashrc:Shell为每个用户维护了一个配置文件,对当前用户有效,推荐;
  • /etc/bash.bashrc:全局shell配置文件,对所有用户有效。
  • source命令生效。

参考文献

Linux从入门到精通 刘忆智 著

文章目录
  1. 1. 正则表达式
  2. 2. Shell脚本编程
    1. 2.1. hello world
    2. 2.2. 变量和运算符
    3. 2.3. 表达式求值
    4. 2.4. 脚本执行命令和控制语句
    5. 2.5. 条件测试
    6. 2.6. 循环结构
    7. 2.7. 脚本执行命令
  3. 3. Shell定制
    1. 3.1. 修改环境变量
  4. 4. 参考文献
|