Linux shell 脚本编程-基础篇 (一)

1. 构建基本脚本



1.1 使用多个命令


shell 脚本的关键在于输入多个命令并处理每个命令的结果,甚至需要将一个命令的结果传给另一个命令。shell可以让将多个命令串起来,一次执行完成。
如果要两个命令一起运行,可以把它们放在同一行中,彼此间用分号隔开。

    [root@devalone testdir]# date; who
    2018年 07月 04日 星期三 19:56:07 CST
    devalone pts/0        2018-07-04 09:17 (192.168.1.101)

使用这种办法就能将任意多个命令串连在一起使用了,只要不超过最大命令行字符数 255 就行。

这种技术对于小型脚本尚可,但它有一个很大的缺陷:每次运行之前,都必须在命令提示符下输入整个命令。可以将这些命令组合成一个简单的文本文件,
这样就不需要在命令行中手动输入了。在需要运行这些命令时,只用运行这个文本文件就行了。


1.2 创建 shell 脚本文件
-----------------------------------------------------------------------------------------------------------------------------------------
要将 shell 命令放到文本文件中,需要用文本编辑器来创建一个文件,然后将命令输入到文件中。

通常以 .sh 作为文件后缀名,也可以没有后缀名。

在创建shell脚本文件时,必须在文件的第一行指定要使用的shell。其格式为:

#!/bin/bash

在通常的shell脚本中,井号(#)用作注释行。shell 并不会处理 shell 脚本中的注释行。然而,shell脚本文件的第一行是个例外,# 后面的惊叹号会告诉
shell 用哪个 shell 来运行脚本(可以使用 bash shell,也可以使用另一个 shell 来运行脚本)。

注释内容:建议养成良好的脚本编写习惯,在每个 script 文件头处记录好如下内容:
    
    ● script 功能
    ● script 版本信息
    ● script 作者与联系方式
    ● script 版权声明
    ● script 历史版本信息
    ● script 内较特殊的指令,使用绝对路径表达
    ● script 运行时所需的环境变量声明与设置

在指定了 shell 之后,就可以在文件的每一行中输入命令,然后加一个回车符。注释可用#添加。
示例:

    #!/bin/bash
    # This script displays the date and who's log on
    echo -n  "The time and date are: "
    date
    echo "Let's see who's logged into the system:"
    who

可以根据需要,使用分号将两个命令放在一行上,但在 shell 脚本中,可以在独立的行中书写命令。shell 会按根据命令在文件中出现的顺序进行处理。

要注意另有一行也以 # 开头,并添加了一个注释。shell 不会解释以 # 开头的行(除了以 #! 开头的第一行)。留下注释来说明脚本做了什么,这种方法
非常好。

要执行脚本文件需要处理两件事:

    
    第一,要让 shell 找到脚本文件
    -------------------------------------------------------------------------------------------------------------------------------------
    要让 bash shell 能找到要执行的脚本文件。需采取以下两种做法之一:

        ● 将 shell 脚本文件所处的目录添加到 PATH 环境变量中;
        ● 在提示符中用绝对或相对文件路径来引用 shell 脚本文件。

    为了引用当前目录下的文件,可以在 shell 中使用单点操作符。

    
    第二,要保证脚本文件对执行者有执行权限
    -------------------------------------------------------------------------------------------------------------------------------------
    通过 chmod 命令赋予用户执行文件的权限:

    [devalone@devalone shell-script]$ chmod a+x test1.sh
    [devalone@devalone shell-script]$ ls -l test1.sh
    -rwxrwxr-x. 1 devalone devalone 152 1月   2 2018 test1.sh


    
■ 脚本的执行规则
-----------------------------------------------------------------------------------------------------------------------------------------
脚本执行遵循如下规则:

    (1) 指令的执行从上而下、从左而右分析执行;
    (2) 指令、选项与参数间的多个空白会被忽略掉;
    (3) 空白行也被忽略掉,[tab] 键空白被视为空格;    
    (4) 读到 Enter 键符号(CR)时,尝试开始执行该行或该串命令;
    (5) 如果一行内容太多,可以使用 "\Enter" 符号来延伸至下一行;
    (6) "#" 被视为注释,任何加在 # 后面的文本被视为注释而被忽略。


■ 脚本执行方法
-----------------------------------------------------------------------------------------------------------------------------------------
    ● 直接下达指令:在子进程中执行脚本
        
        绝对路径:/home/devalone/shell.sh
        相对路径:./shell.sh
        环境变量 "PATH" 所包含的路径:shell.sh

    ● 以 bash 程序来执行: 在子进程中执行脚本
            
        bash shell.sh
        sh shell.sh
    
        观查:
            [devalone@devalone 19]$ which sh
            /usr/bin/sh
            [devalone@devalone 19]$ ll /usr/bin/sh
            lrwxrwxrwx. 1 root root 4 9月  30 2016 /usr/bin/sh -> bash
        
            sh 是 bash 的连接,因此 sh 可以直接执行脚本。
    
    ● source 或 .    :在父进程中执行脚本
    
        source shell.sh
        . shell.sh

        
■ 脚本的追踪与 debug
-----------------------------------------------------------------------------------------------------------------------------------------    
script 在执行之前,通过 bash 相关参数判断可以判断脚本中是否有语法错误。

[devalone@devalone shell-script]$ sh [-nvx] scripts.sh

选项:
    -n :不执行 script,仅检查语法问题;
    -v :在执行 sccript 之前,先将 scripts 的内容输出到屏幕上;
    -x :将 script 的执行过程显示到屏幕上。
    
示例: 检查 test1.sh 是否有语法问题

    [devalone@devalone shell-script]$ sh -n test1.sh
    [devalone@devalone shell-script]$ 语法没有问题,不会显示任何信息
    
    
示例:使用 -v 选项显示脚本内容及其执行过程

    [devalone@devalone shell-script]$ sh -v test1.sh
    module () {  eval `/usr/bin/modulecmd bash $*`
    }
    scl () {  local CMD=$1;
     if [ "$CMD" = "load" -o "$CMD" = "unload" ]; then
     eval "module $@";
     else
     /usr/bin/scl "$@";
     fi
    }
    #!/bin/bash

    # This script displays the date and who's log on
    echo -n  "The time and date are: "
    The time and date are: date
    2018年 07月 10日 星期二 15:53:27 CST
    echo "Let's see who's logged into the system:"
    Let's see who's logged into the system:
    who
    devalone pts/0        2018-07-10 10:18 (192.168.1.101)


示例: 将 test1.sh 的执行过程显示到屏幕上:

    [devalone@devalone shell-script]$ sh -x test1.sh
    + echo -n 'The time and date are: '
    The time and date are: + date
    2018年 07月 10日 星期二 15:50:31 CST
    + echo 'Let'\''s see who'\''s logged into the system:'
    Let's see who's logged into the system:
    + who
    devalone pts/0        2018-07-10 10:18 (192.168.1.101)


1.3 显示消息
-----------------------------------------------------------------------------------------------------------------------------------------
在 echo 命令后面加上了一个字符串,该命令就能显示出这个文本字符串。

    [devalone@devalone shell-script]$ echo this is a test
    this is a test

默认情况下,不需要使用引号将要显示的文本字符串划定出来。但有时在字符串中出现引号的话就比较麻烦了。echo 命令可用单引号或双引号来划定文本
字符串。如果在字符串中用到了它们,需要在文本中使用其中一种引号,而用另外一种来将字符串划定起来。

    [devalone@devalone shell-script]$ echo "This is a test to see if you're paying attention"
    This is a test to see if you're paying attention

可以将 echo 语句添加到 shell 脚本中任何需要显示额外信息的地方。

    [devalone@devalone shell-script]$ cat test1.sh
    #!/bin/bash
    
    # This script displays the date and who's log on
    echo -n  "The time and date are: "
    date
    echo "Let's see who's logged into the system:"
    who

设置 PATH 环境变量:将当前工作目录加入 PATH 路径中。
    
    [devalone@devalone shell-script]$ PATH=$PATH:.
    
运行:
    [devalone@devalone shell-script]$ test1.sh
    The time and date are: 2018年 07月 04日 星期三 20:23:44 CST
    Let's see who's logged into the system:
    devalone pts/0        2018-07-04 09:17 (192.168.1.101)

如果想把文本字符串和命令输出显示在同一行中,可以用 echo 语句的 -n 参数。只要将第一个 echo 语句改成这样就行:
如上例所示。

1.4 使用变量
-----------------------------------------------------------------------------------------------------------------------------------------
变量允许临时性地将信息存储在 shell 脚本中,以便和脚本中的其他命令一起使用。


1.4.1 环境变量
-----------------------------------------------------------------------------------------------------------------------------------------
Linux 系统的环境变量,也可以在脚本中访问。

shell 维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用户名、用户的系统ID(也称为UID)、用户的默认主目录以及
shell 查找程序的搜索路径。可以用 set 命令来显示一份完整的当前环境变量列表。

    [devalone@devalone ~]$ set
    BASH=/bin/bash
    BASHOPTS=checkwinsize:cmdhist:complete_fullquote:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments
    :login_shell:progcomp:promptvars:sourcepath
    BASH_ALIASES=()
    BASH_ARGC=()
    BASH_ARGV=()
    BASH_CMDS=()
    BASH_COMPLETION_COMPAT_DIR=/etc/bash_completion.d
    BASH_LINENO=()
    BASH_REMATCH=()
    BASH_SOURCE=()
    BASH_VERSINFO=([0]="4" [1]="3" [2]="42" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
    BASH_VERSION='4.3.42(1)-release'
    CATALINA_HOME=/home/devalone/programs/apache-tomcat-8.5.11
    COLUMNS=130
    COMP_WORDBREAKS=$' \t\n"\'><=;|&(:'
    DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
    DIRSTACK=()
    EUID=1000
    GROUPS=()
    HISTCONTROL=ignoredups
    HISTFILE=/home/devalone/.bash_history
    HISTFILESIZE=1000
    HISTSIZE=1000
    HOME=/home/devalone
    HOSTNAME=devalone.sansovo.org
    ...

在脚本中,可以在环境变量名称之前加上美元符($)来使用这些环境变量:
    
    [devalone@devalone shell-script]$ cat test2.sh
    #!/bin/bash
    # display user information from the system.
    echo "User info for userid: $USER"
    echo UID: $UID
    echo HOME: $HOME

$USER、$UID 和 $HOME 环境变量用来显示已登录用户的有关信息。脚本输出如下:

    [devalone@devalone shell-script]$ ./test2.sh
    User info for userid: devalone
    UID: 1000
    HOME: /home/devalone

注意,echo 命令中的环境变量会在脚本运行时替换成当前值。另外,在第一个字符串中可以将 $USER 系统变量放置到双引号中,而 shell 依然能够知道
我们的意图。


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    可以通过 ${variable} 形式引用的变量。变量名两侧额外的花括号通常用来帮助识别美元符后的变量名。
    
    
1.4.2 用户变量
-----------------------------------------------------------------------------------------------------------------------------------------
除了环境变量,shell 脚本还允许在脚本中定义和使用自己的变量。定义变量允许临时存储数据并在整个脚本中使用,从而使shell脚本看起来更像一个真正
的计算机程序。

用户变量可以是任何由字母、数字或下划线组成的文本字符串,长度不超过20个。用户变量区分大小写。

使用等号将值赋给用户变量。在变量、等号和值之间不能出现空格。

示例:

    var1=10
    var2=-57
    var3=testing
    var4="still more testing"

shell 脚本会自动决定变量值的数据类型。在脚本的整个生命周期里,shell 脚本中定义的变量会一直保持着它们的值,但在shell脚本结束时会被删除掉。

与系统变量类似,用户变量可通过美元符 ($) 引用。

示例:
    [devalone@devalone shell-script]$ cat test3.sh
    #!/bin/bash
    # testing variables
    days=10
    guest="Katie"
    echo "$guest checked in $days days ago"

    days=5
    guest="Jessical"
    echo "$guest checked in $days days ago"

运行:
    [devalone@devalone shell-script]$ ./test3.sh
    Katie checked in 10 days ago
    Jessical checked in 5 days ago

变量每次被引用时,都会输出当前赋给它的值。重要的是要记住,引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不要使用美元符。

示例:
    [devalone@devalone shell-script]$ cat test4.sh
    #!/bin/bash
    # assigning a variable value to another variable

    value1=10
    value2=$value1
    echo The resulting value is $value2

在赋值语句中使用value1变量的值时,仍然必须用美元符。

运行:
    [devalone@devalone shell-script]$ ./test4.sh
    The resulting value is 10

没有美元符,shell 会将变量名解释成普通的文本字符串,通常这并不是想要的结果。


1.4.3 命令替换
-----------------------------------------------------------------------------------------------------------------------------------------
shell 脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了。这个特性在处理脚本
数据时尤为方便。

有两种方法可以将命令输出赋给变量:

    □ 反引号字符(`)
    □ $()格式

命令替换允许用户将 shell 命令的输出赋给变量。

用一对反引号把整个命令行命令围起来:

    testing='date'
    
使用$()格式:

    testing=$(date)

shell 会运行命令替换符号中的命令,并将其输出赋给变量 testing。注意,赋值等号和命令替换字符之间没有空格。

    [devalone@devalone shell-script]$ cat test5.sh
    #!/bin/bash
    testing=$(date)
    echo "The date and time are: " $testing

变量 testing 获得了 date 命令的输出,然后使用 echo 语句显示出它的值。

运行:
    [devalone@devalone shell-script]$ ./test5.sh
    The date and time are:  2018年 07月 05日 星期四 11:31:37 CST

示例:
    [devalone@devalone shell-script]$ cat test6.sh
    #!/bin/bash
    # copy the /usr/bin directory listing to a log file
    today=$(date +%y%m%d)
    ls /usr/bin -al > log.$today

在脚本中通过命令替换获得当前日期并用它来生成唯一文件名。today 变量是被赋予格式化后的 date 命令的输出。这是提取日期信息来生成日志文件名常用
的一种技术。+%y%m%d格式告诉date命令将日期显示为两位数的年月日的组合。

运行:
    [devalone@devalone shell-script]$ ./test6.sh
    [devalone@devalone shell-script]$ ls -l log*
    -rw-rw-r--. 1 devalone devalone 122402 7月   5 11:45 log.180705

生成了以当前日期作为文件名后缀的文件:log.180705


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    命令替换会创建一个子shell 来运行对应的命令。子shell(subshell)是由运行该脚本的 shell 所创建出来的一个独立的子shell(child shell)。
    正因如此,由该子shell 所执行命令是无法使用脚本中所创建的变量的。
    
    在命令行提示符下使用路径./运行命令的话,也会创建出子shell;要是运行命令的时候不加入路径,就不会创建子shell。如果使用的是内建的 shell
    命令,并不会涉及子shell。
    
    
1.5 重定向输入和输出
-----------------------------------------------------------------------------------------------------------------------------------------
有些时候想要保存某个命令的输出而不仅仅只是让它显示在显示器上。bash shell提供了几个操作符,可以将命令的输出重定向到另一个位置(比如文件)。
重定向可以用于输入,也可以用于输出,可以将文件重定向到命令输入。


1.5.1 输出重定向
-----------------------------------------------------------------------------------------------------------------------------------------
最基本的重定向将命令的输出发送到一个文件中。bash shell 用大于号(>)来完成这项功能:

    command > outputfile

示例:
    [devalone@devalone shell-script]$ date > testdate
    [devalone@devalone shell-script]$ ls -l testdate
    -rw-rw-r--. 1 devalone devalone 43 7月   5 11:58 testdate

    [devalone@devalone shell-script]$ cat testdate
    2018年 07月 05日 星期四 11:58:19 CST

重定向操作符创建了一个文件 testdate(通过默认的 umask 设置),并将 date 命令的输出重定向到该文件中。如果输出文件已经存在了,重定向操作符
会用新的文件数据覆盖已有文件。    

示例:
    [devalone@devalone shell-script]$ who >testdate
    [devalone@devalone shell-script]$ cat testdate
    devalone pts/0        2018-07-05 11:05 (192.168.1.101)

testdate 内容被覆盖。

如果不想覆盖文件原有内容,而是想要将命令的输出追加到已有文件中,比如正在创建一个记录系统上某个操作的日志文件。在这种情况下,可以用双大于号
>> 来追加数据。

示例:
    [devalone@devalone shell-script]$ date >> testdate
    [devalone@devalone shell-script]$ cat testdate
    devalone pts/0        2018-07-05 11:05 (192.168.1.101)
    2018年 07月 05日 星期四 12:03:51 CST


1.5.2 输入重定向
-----------------------------------------------------------------------------------------------------------------------------------------
输入重定向和输出重定向正好相反。输入重定向将文件的内容重定向到命令,而非将命令的输出重定向到文件。

输入重定向符号是小于号(<):

    command < inputfile

一个简单的记忆方法就是:在命令行上,命令总是在左侧,而重定向符号 "指向" 数据流动的方向。小于号说明数据正在从输入文件流向命令。

示例:
    [devalone@devalone shell-script]$ wc < testdate
     2 11 98

wc 命令可以对对数据中的文本进行计数。默认情况下,它会输出3个值:

    □ 文本的行数
    □ 文本的词数
    □ 文本的字节数
    
通过将文本文件重定向到 wc 命令,立刻就可以得到文件中的行、词和字节的计数。这个例子说明 testdate 文件有2行、11个 单词以及 98 字节。

还有另外一种输入重定向的方法,称为内联输入重定向(inline input redirection)。这种方法无需使用文件进行重定向,只需要在命令行中指定用于输入
重定向的数据就可以了。

内联输入重定向符号是远小于号(<<)。除了这个符号,必须指定一个文本标记来划分输入数据的开始和结尾。任何字符串都可作为文本标记,但在数据的
开始和结尾文本标记必须一致。

命令格式:

    command << marker
    data
    marker
    
在命令行上使用内联输入重定向时,shell 会用 PS2 环境变量中定义的次提示符来提示输入数据。下面是它的使用情况。    

示例:

    [devalone@devalone shell-script]$ wc << EOF
    > test string 1
    > test string 2
    > test string 3
    > EOF
     3  9 42

次提示符会持续提示,以获取更多的输入数据,直到输入了作为文本标记的那个字符串。wc 命令会对内联输入重定向提供的数据进行行、词和字节计数。


1.6 管道
-----------------------------------------------------------------------------------------------------------------------------------------
有时需要将一个命令的输出作为另一个命令的输入。这可以用重定向来实现,只是有些笨拙。

如:
    [devalone@devalone shell-script]$ rpm -qa > rpm.list
    [devalone@devalone shell-script]$ sort < rpm.list
    aajohan-comfortaa-fonts-2.004-6.fc24.noarch
    abattis-cantarell-fonts-0.0.24-1.fc24.noarch
    abrt-2.8.2-1.fc24.x86_64
    abrt-addon-ccpp-2.8.2-1.fc24.x86_64
    abrt-addon-coredump-helper-2.8.2-1.fc24.x86_64
    abrt-addon-kerneloops-2.8.2-1.fc24.x86_64
    abrt-addon-pstoreoops-2.8.2-1.fc24.x86_64
    abrt-addon-python3-2.8.2-1.fc24.x86_64
    abrt-addon-vmcore-2.8.2-1.fc24.x86_64
    abrt-addon-xorg-2.8.2-1.fc24.x86_64
    abrt-cli-2.8.2-1.fc24.x86_64
    abrt-dbus-2.8.2-1.fc24.x86_64
    abrt-desktop-2.8.2-1.fc24.x86_64
    ...

这种方法的确管用,但仍然是一种比较繁琐的信息生成方式。我们用不着将命令输出重定向到文件中,可以将其直接重定向到另一个命令。这个过程叫作
管道连接(piping)。

管道被放在命令之间,将一个命令的输出重定向到另一个命令中:

    command1 | command2
    
不要以为由管道串起的两个命令会依次执行。Linux 系统实际上会同时运行这两个命令,在系统内部将它们连接起来。在第一个命令产生输出的同时,输出
会被立即送给第二个命令。数据传输不会用到任何中间文件或缓冲区。

    示例:
    [devalone@devalone shell-script]$ rpm -qa | sort
    aajohan-comfortaa-fonts-2.004-6.fc24.noarch
    abattis-cantarell-fonts-0.0.24-1.fc24.noarch
    abrt-2.8.2-1.fc24.x86_64
    abrt-addon-ccpp-2.8.2-1.fc24.x86_64
    abrt-addon-coredump-helper-2.8.2-1.fc24.x86_64
    abrt-addon-kerneloops-2.8.2-1.fc24.x86_64
    abrt-addon-pstoreoops-2.8.2-1.fc24.x86_64
    abrt-addon-python3-2.8.2-1.fc24.x86_64
    abrt-addon-vmcore-2.8.2-1.fc24.x86_64
    abrt-addon-xorg-2.8.2-1.fc24.x86_64
    abrt-cli-2.8.2-1.fc24.x86_64
    abrt-dbus-2.8.2-1.fc24.x86_64
    abrt-desktop-2.8.2-1.fc24.x86_64
    abrt-gui-2.8.2-1.fc24.x86_64
    abrt-gui-libs-2.8.2-1.fc24.x86_64
    ...


可以在一条命令中使用任意多条管道。可以持续地将命令的输出通过管道传给其他命令来细化操作。

示例:
    
    [devalone@devalone shell-script]$ rpm -qa | sort | less
    aajohan-comfortaa-fonts-2.004-6.fc24.noarch
    abattis-cantarell-fonts-0.0.24-1.fc24.noarch
    abrt-2.8.2-1.fc24.x86_64
    abrt-addon-ccpp-2.8.2-1.fc24.x86_64
    abrt-addon-coredump-helper-2.8.2-1.fc24.x86_64
    abrt-addon-kerneloops-2.8.2-1.fc24.x86_64
    abrt-addon-pstoreoops-2.8.2-1.fc24.x86_64
    abrt-addon-python3-2.8.2-1.fc24.x86_64
    abrt-addon-vmcore-2.8.2-1.fc24.x86_64
    abrt-addon-xorg-2.8.2-1.fc24.x86_64
    abrt-cli-2.8.2-1.fc24.x86_64
    abrt-dbus-2.8.2-1.fc24.x86_64
    abrt-desktop-2.8.2-1.fc24.x86_64
    abrt-gui-2.8.2-1.fc24.x86_64
    abrt-gui-libs-2.8.2-1.fc24.x86_64
    ...


也可以搭配使用重定向和管道来将输出保存到文件中:

    [devalone@devalone shell-script]$ rpm -qa | sort | less
    [devalone@devalone shell-script]$ rpm -qa | sort > rpm.list
    [devalone@devalone shell-script]$ less rpm.list
    aajohan-comfortaa-fonts-2.004-6.fc24.noarch
    abattis-cantarell-fonts-0.0.24-1.fc24.noarch
    abrt-2.8.2-1.fc24.x86_64
    abrt-addon-ccpp-2.8.2-1.fc24.x86_64
    abrt-addon-coredump-helper-2.8.2-1.fc24.x86_64
    abrt-addon-kerneloops-2.8.2-1.fc24.x86_64
    abrt-addon-pstoreoops-2.8.2-1.fc24.x86_64
    abrt-addon-python3-2.8.2-1.fc24.x86_64
    abrt-addon-vmcore-2.8.2-1.fc24.x86_64
    abrt-addon-xorg-2.8.2-1.fc24.x86_64
    abrt-cli-2.8.2-1.fc24.x86_64
    abrt-dbus-2.8.2-1.fc24.x86_64
    abrt-desktop-2.8.2-1.fc24.x86_64
    abrt-gui-2.8.2-1.fc24.x86_64
    abrt-gui-libs-2.8.2-1.fc24.x86_64
    ...

rpm.list文件中的数据现在已经排好序了。


1.7 执行数学运算
-----------------------------------------------------------------------------------------------------------------------------------------
另一个对任何编程语言都很重要的特性是操作数字的能力。遗憾的是,对 shell 脚本来说,这个处理过程会比较麻烦。在 shell 脚本中有两种途径来进行
数学运算。


1.7.1 expr 命令
-----------------------------------------------------------------------------------------------------------------------------------------
最开始,Bourne shell 提供了一个特别的命令用来处理数学表达式。expr 命令允许在命令行上处理数学表达式,但是特别笨拙。

    [devalone@devalone shell-script]$ expr 1 + 5
    6

expr 命令能够识别少数的数学和字符串操作符,见下表:

    expr 命令操作符
    +---------------------------+-----------------------------------------------------------------------
    | 操 作 符                    | 描 述
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 | ARG2                | 如果ARG1既不是null也不是零值,返回ARG1;否则返回ARG2
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 & ARG2                | 如果没有参数是null或零值,返回ARG1;否则返回0
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 < ARG2                | 如果ARG1小于ARG2,返回1;否则返回0
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 <= ARG2                | 如果ARG1小于或等于ARG2,返回1;否则返回0
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 = ARG2                | 如果ARG1等于ARG2,返回1;否则返回0
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 != ARG2                | 如果ARG1不等于ARG2,返回1;否则返回0
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 >= ARG2                | 如果ARG1大于或等于ARG2,返回1;否则返回0
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 > ARG2                | 如果ARG1大于ARG2,返回1;否则返回0
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 + ARG2                | 返回ARG1和ARG2的算术运算和
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 - ARG2                | 返回ARG1和ARG2的算术运算差
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 * ARG2                | 返回ARG1和ARG2的算术乘积
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 / ARG2                | 返回ARG1被ARG2除的算术商
    +---------------------------+-----------------------------------------------------------------------
    | ARG1 % ARG2                | 返回ARG1被ARG2除的算术余数
    +---------------------------+-----------------------------------------------------------------------
    | STRING : REGEXP            | 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配
    +---------------------------+-----------------------------------------------------------------------
    | match STRING REGEXP        | 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配
    +---------------------------+-----------------------------------------------------------------------
    | substr STRING POS LENGTH    | 返回起始位置为POS(从1开始计数)、长度为LENGTH个字符的子字符串        
    +---------------------------+-----------------------------------------------------------------------
    | index STRING CHARS        | 返回在STRING中找到CHARS字符串的位置;否则,返回0
    +---------------------------+-----------------------------------------------------------------------
    | length STRING                | 返回字符串STRING的数值长度
    +---------------------------+-----------------------------------------------------------------------
    | + TOKEN                    | 将TOKEN解释成字符串,即使是个关键字
    +---------------------------+-----------------------------------------------------------------------
    | (EXPRESSION)                | 返回EXPRESSION的值
    +---------------------------+-----------------------------------------------------------------------
    
expr 支持模式匹配和字符串操作。字符串表达式的优先级高于数值表达式和逻辑关系表达式。
 
'STRING : REGEX'
     执行模式匹配。两端参数会转换为字符格式,且第二个参数被视为正则表达式(GNU基本正则),它默认会隐含前缀"^"。随后将第一个参数和正则模式做匹配。
 
     如果匹配成功,且REGEX使用了'\('和'\)',则此表达式返回匹配到的,如果未使用'\('和'\)',则返回匹配的字符数。
 
     如果匹配失败,如果REGEX中使用了'\('和'\)',则此表达式返回空字符串,否则返回为0。
 
     只有第一个'\(...\)'会引用返回的值;其余的'\(...\)'只在正则表达式分组时有意义。
 
     在正则表达式中,'\+','\?'和'\|'分表代表匹配一个或多个,0个或1个以及两端任选其一的意思。
 
'match STRING REGEX'
     等价于'STRING : REGEX'。
 
'substr STRING POSITION LENGTH'
     返回STRING字符串中从POSITION开始,长度最大为LENGTH的子串。如果POSITION或LENGTH为负数,0或非数值,则返回空字符串。
 
'index STRING CHARSET'
     CHARSET中任意单个字符在STRING中最前面的字符位置。如果在STRING中完全不存在CHARSET中的字符,则返回0。见后文示例。
    
'length STRING'
     返回STRING的字符长度。
 
'+ TOKEN'
     将TOKEN解析为普通字符串,即使TOKEN是像MATCH或操作符"/"一样的关键字。这使得'expr length + "$x"'或'expr + "$x" : '.*/\(.\)''可以正常被测试,即使"$x"的值可能是'/'或'index'关键字。这个操作符是一个GUN扩展。
     通用可移植版的应该使用'" $token" : ' \(.*\)''来代替'+ "$token"'。
 
   要让expr将关键字解析为普通的字符,必须使用引号包围。
   

尽管标准操作符在 expr 命令中工作得很好,但在脚本或命令行上使用它们时仍有问题出现。许多 expr 命令操作符在shell中另有含义(比如星号)。
当它们出现在在 expr 命令中时,会得到一些诡异的结果。

示例:
    [devalone@devalone shell-script]$ expr 5 * 2
    expr: 语法错误

要解决这个问题,对于那些容易被 shell 错误解释的字符,在它们传入 expr 命令之前,需要使用 shell 的转义字符(反斜线)将其标出来。

    [devalone@devalone shell-script]$ expr 5 \* 2
    10

在shell脚本中使用expr命令也同样复杂:

    [devalone@devalone shell-script]$ cat test6
    #!/bin/bash
    # An example of using the expr command
    var1=10
    var2=20
    var3=$(expr $var2 / $var1)
    echo The result is $var3

要将一个数学算式的结果赋给一个变量,需要使用命令替换来获取 expr 命令的输出:

运行:
    [devalone@devalone shell-script]$ test6
    The result is 2


1.7.2 使用方括号[]
-----------------------------------------------------------------------------------------------------------------------------------------
bash shell 为了保持跟 Bourne shell的兼容而包含了 expr 命令,但它同样也提供了一种更简单的方法来执行数学表达式。在 bash 中,在将一个数学运算
结果赋给某个变量时,可以用美元符和方括号($[ operation ])将数学表达式围起来。

示例:
    [devalone@devalone shell-script]$ var1=$[1 + 5]
    [devalone@devalone shell-script]$ echo $var1
    6
    [devalone@devalone shell-script]$ var2=$[$var1 * 2]
    [devalone@devalone shell-script]$ echo $var2
    12

用方括号执行 shell 数学运算比用 expr 命令方便很多。这种技术也适用于 shell 脚本。

示例:
    [devalone@devalone shell-script]$ cat test7.sh
    #!/bin/bash
    var1=100
    var2=50
    var3=45
    var4=$[$var1 * ($var2 - $var3)]
    echo The final result is $var4

运行:
    [devalone@devalone shell-script]$ chmod a+x test7.sh
    [devalone@devalone shell-script]$ test7.sh
    The final result is 500

在使用方括号来计算公式时,不用担心 shell 会误解乘号或其他符号。shell 知道它不是通配符,因为它在方括号内。

在 bash shell脚本中进行算术运算会有一个主要的限制,bash shell数学运算符只支持整数运算。若要进行任何实际的数学计算,这是一个巨大的限制。

示例:

    [devalone@devalone shell-script]$ cat test8.sh
    #!/bin/bash
    var1=100
    var2=45
    var3=$[$var1 / $var2]
    echo The final result is $var3

运行:
    [devalone@devalone shell-script]$ test8.sh
    The final result is 2

    
    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    z shell(zsh)提供了完整的浮点数算术操作。如果需要在shell脚本中进行浮点数运算,可以考虑看看z shell
    
    
1.7.3 浮点解决方案
-----------------------------------------------------------------------------------------------------------------------------------------
有几种解决方案能够克服 bash 中数学运算的整数限制。最常见的方案是用内建的 bash 计算器,叫作bc。


■ bc 的基本用法
-----------------------------------------------------------------------------------------------------------------------------------------
bash 计算器实际上是一种编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。bash 计算器能够识别:

    □ 数字(整数和浮点数)
    □ 变量(简单变量和数组)
    □ 注释(以#或C语言中的/* */开始的行)
    □ 表达式
    □ 编程语句(例如if-then语句)
    □ 函数

可以在 shell 提示符下通过 bc 命令访问 bash 计算器:

    [devalone@devalone shell-script]$ bc
    bc 1.06.95
    Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
    This is free software with ABSOLUTELY NO WARRANTY.
    For details type `warranty'.
    12 * 5.4
    64.8
    3.156 * (3 + 5)
    25.248
    quit

要退出 bash 计算器,必须输入 quit。

浮点运算是由内建变量 scale 控制的。必须将这个值设置为希望在计算结果中保留的小数位数,否则无法得到期望的结果。

示例:

    [devalone@devalone shell-script]$ bc -q
    3.44 / 5
    0
    scale=4
    3.44/5
    .6880
    quit

scale 变量的默认值是 0。在 scale 值被设置前,bash 计算器的计算结果不包含小数位。在将其值设置成 4 后,bash 计算器显示的结果包含四位小数。
-q 命令行选项可以不显示 bash 计算器冗长的欢迎信息。

除了普通数字,bash 计算器还能支持变量。

示例:
    [devalone@devalone shell-script]$ bc -q
    var1=10
    var1 * 4
    40
    var2=var1 / 5
    print var2
    2
    quit

变量一旦被定义,就可以在整个 bash 计算器会话中使用该变量了。print 语句允许打印变量和数字。

■ 在脚本中使用 bc
-----------------------------------------------------------------------------------------------------------------------------------------
可以用命令替换运行 bc 命令,并将输出赋给一个变量。基本格式如下:

    variable=$(echo "options; expression" | bc)

第一部分 options 允许设置变量。如果需要不止一个变量,可以用分号将其分开。expression 参数定义了通过 bc 执行的数学表达式。

示例:

    [devalone@devalone shell-script]$ cat test9.sh
    #!/bin/bash
    var1=$(echo "scale=4; 3.44/5" | bc)
    echo The answer is $var1

运行:
    [devalone@devalone shell-script]$ test9.sh
    The answer is .6880

示例:
    [devalone@devalone shell-script]$ cat test10.sh
    #!/bin/bash
    var1=100
    var2=45
    var3=$(echo "scale=4; $var1/$var2" | bc)
    echo The answer for this is $var3

运行:
    [devalone@devalone shell-script]$ test10.sh
    The answer for this is 2.2222

当然,一旦变量被赋值,那个变量也可以用于其他运算。

示例:
[devalone@devalone shell-script]$ cat test11.sh
#!/bin/bash
var1=20
var2=3.14159
var3=$(echo "scale=4; $var1 * $var1" | bc)
var4=$(echo "scale=4; $var3 * $var2" | bc)
echo The final result is $var4

输出:
    [devalone@devalone shell-script]$ test11.sh
    The final result is 1256.63600

这个方法适用于较短的运算,但有时会涉及更多的数字。如果需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦。

有一个方法可以解决这个问题。bc 命令能识别输入重定向,允许将一个文件重定向到 bc 命令来处理。但这同样会叫人头疼,因为还得将表达式存放到
文件中。


最好的办法是使用内联输入重定向,它允许直接在命令行中重定向数据。在 shell 脚本中,可以将输出赋给一个变量。
如下形式:

    variable=$(bc << EOF
    options
    statements
    expressions
    EOF
    )

EOF 文本字符串标识了内联重定向数据的起止。记住,仍然需要命令替换符号将 bc 命令的输出赋给变量。

现在可以将所有 bash 计算器涉及的部分都放到同一个脚本文件的不同行。

示例:
    
    [devalone@devalone shell-script]$ cat test12.sh
    #!/bin/bash
    var1=10.46
    var2=43.67
    var3=33.2
    var4=71

    var5=$(bc <<EOF
    scale=4
    al=($var1 * $var2)
    b1=($var3 * $var4)
    a1+b1
    EOF
    )

    echo The final answer for this mess is $var5

运行:
    [devalone@devalone shell-script]$ test12.sh
    The final answer for this mess is 2357.2


将选项和表达式放在脚本的不同行中可以让处理过程变得更清晰,提高易读性。EOF 字符串标识了重定向给 bc 命令的数据的起止。当然,必须用命令替换符号
标识出用来给变量赋值的命令。

还会注意到,在这个例子中,可以在 bash 计算器中赋值给变量。这一点很重要:在 bash 计算器中创建的变量只在 bash 计算器中有效,不能在 shell
脚本中使用。


1.8 退出脚本
-----------------------------------------------------------------------------------------------------------------------------------------
shell 中运行的每个命令都使用退出状态码(exit status)告诉 shell 它已经运行完毕。退出状态码是一个 0~255 的整数值,在命令结束运行时由命令
传给 shell。可以捕获这个值并在脚本中使用。


1.8.1 查看退出状态码
-----------------------------------------------------------------------------------------------------------------------------------------
Linux 提供了一个专门的变量 $? 来保存上个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用 $? 变量。它的值
会变成由 shell 所执行的最后一条命令的退出状态码。

示例:
    [devalone@devalone shell-script]$ who
    devalone pts/0        2018-07-05 11:05 (192.168.1.101)
    [devalone@devalone shell-script]$ echo $?
    0

一个成功结束的命令的退出状态码是 0。如果一个命令结束时有错误,退出状态码就是一个正数值。

示例:
    [devalone@devalone shell-script]$ zzzz
    bash: zzzz: 未找到命令...
    [devalone@devalone shell-script]$ echo $?
    127

无效命令会返回一个退出状态码 127。Linux 错误退出状态码没有什么标准可循,但有一些可用的参考,如下表:

        Linux 退出状态码
        +-----------+--------------------------------
        | 状态码    | 描 述
        +-----------+--------------------------------
        | 0            | 命令成功结束
        +-----------+--------------------------------
        | 1            | 一般性未知错误
        +-----------+--------------------------------
        | 2            | 不适合的shell命令
        +-----------+--------------------------------
        | 126        | 命令不可执行
        +-----------+--------------------------------
        | 127        | 没找到命令
        +-----------+--------------------------------
        | 128        | 无效的退出参数
        +-----------+--------------------------------
        | 128+x        | 与Linux信号x相关的严重错误
        +-----------+--------------------------------
        | 130        | 通过Ctrl+C终止的命令
        +-----------+--------------------------------
        | 255        | 正常范围之外的退出状态码        
        +-----------+--------------------------------
        
        
退出状态码 126 表明用户没有执行命令的正确权限:

    [devalone@devalone shell-script]$ testfile
    -bash: ./testfile: Permission denied
    [devalone@devalone shell-script]$ echo $?
    126

另一个会碰到的常见错误是给某个命令提供了无效参数:

    [devalone@devalone shell-script]$ date %t
    date: 无效的日期"%t"
    [devalone@devalone shell-script]$ echo $?
    1

    
1.8.2 exit 命令
-----------------------------------------------------------------------------------------------------------------------------------------
默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。可以改变这种默认行为,返回自己的退出状态码。exit 命令允许在脚本结束时指定
一个退出状态码。

示例:
    [devalone@devalone shell-script]$ cat test13.sh
    #!/bin/bash
    # testing the exit status

    var1=10
    var2=30
    var3=$[$var1 + $var2]
    echo The answer is $var3
    exit 5
    
运行:
    [devalone@devalone shell-script]$ test13.sh
    The answer is 40
    [devalone@devalone shell-script]$ echo $?
    5

    
也可以在exit命令的参数中使用变量:

    [devalone@devalone shell-script]$ cat test14.sh
    #!/bin/bash
    # testing the exit status
    var1=10
    var2=30
    var3=$[$var1 + $var2]
    exit $var3

运行:
    [devalone@devalone shell-script]$ test14.sh
    [devalone@devalone shell-script]$ echo $?
    40

要注意这个功能,退出状态码最大只能 255,如果超出 255,shell 通过模运算得到这个结果。一个值的模就是被除后的余数。最终的结果是指定的数值
除以 256 后得到的余数:

示例:
    [devalone@devalone shell-script]$ cat test14b.sh
    #!/bin/bash
    # testing the exit status
    var1=10
    var2=30
    var3=$[$var1 * $var2]
    echo The value is $var3
    exit $var3

运行:
    [devalone@devalone shell-script]$ test14b.sh
    The value is 300
    [devalone@devalone shell-script]$ echo $?
    44

系列目录:

    Linux shell 脚本编程-基础篇 (一)

    Linux shell 脚本编程-基础篇 (二)

    Linux shell 脚本编程-基础篇 (三)

    Linux shell 脚本编程-基础篇 (四)

    Linux shell 脚本编程-基础篇 (五)

    Linux shell 脚本编程-基础篇 (六)

-----------------------------------------------------------------------------------------------------------------------------------------
参考:

    《Linux 命令行与 shell 脚本编程大全》 第 3 版 —— 2016.8(美)Richard Blum  Cristine Bresnahan

猜你喜欢

转载自blog.csdn.net/devalone/article/details/81215492
今日推荐