• 最近开发需要在linux服务器上做大数据处理,由于对Linux开发并不是很熟悉,因此踩了很多坑,先作如下记录:1.bash shell实现多进程背景如下:需要从hadoop的hdfs上向服务器下拉数据,共7200多个数据文件,每个文件约1...

    最近开发需要在linux服务器上做大数据处理,由于对Linux开发并不是很熟悉,因此踩了很多坑,先作如下记录:

    1.bash shell实现多进程

    背景如下:需要从hadoop的hdfs上向服务器下拉数据,共7200多个数据文件,每个文件约1G左右,共7T左右的数据。
    单纯的我一开始写了个shell脚本文件然后开始拉数据,结果发现拉了一下午,也才下载了100多个文件。。。照此速度基本得拉倒天荒地老了。。。多核的机器得用起来啊,于是开始研究如何在bash shell中实现多进程同时拉数据。参考了一些教程,主要有以下三点技能需要get:
    1.for 循环
    2.后台运行符&
    3.wait等待所有子进程结束
    get到了这3点技能后,说干就干的我开始写出了如下的脚本代码:

    #!/bin/bash
    
    for((i=0;i<7252;i++));do
    {
        #拉取第i个文件
    }&
    done
    wait

    保存,运行~动作完美和谐,一气呵成。然而。。呵呵。。机器挂了。。。。世界陷入了一片死寂。。。
    不明真相的我开始找运维。。运维说是机器背部电源板老化导致宕机。修复后重新登上机器,我又准备运行我的脚本,然而不出所料,机器再一次挂掉了。。。我觉得运维应该有杀了我全家的冲动。。。
    此时的我终于意识到应该是我的脚本文件导致机器挂掉。于是开始仔细反思,在脚本中,我设定了循环中的变量从0到7151,后台运行符表明此段应该要后台并发执行,所以意味着我要同时开7252个进程去执行这段代码。。。再强大的机器也没这么多核。。。理解了多进程原理后,找到症结的我重新修改了代码:

    #!/bin/bash
    
    for((i=0;i<7252;i+=10));do
    {
        for((j=0;j<10;j++));do
        {
            index=$(expr $i + $j)
            #拉取第index个文件
        }&
        done 
        wait
    }
    done

    上面的代码中,每次同时开10个进程拉取10个文件,等这10个进程都拉取完毕后,再开10个进程拉取下一批10个文件,直到把所有的7252个文件拉取完毕。
    总结这个坑,是因为对Linux多进程实现机制不了解所致,以后再写多进程脚本时,应该会吃一堑长一智了。

    2.TAB的ascii码

    写下这个小标题的时候我的内心是崩溃的,这种初学计算机的菜鸟都应该懂的知识,我一个在BAT互联网工作了两年的工程师居然都是后知后觉才Get到。
    背景是这样的:
    上面提到的这些数据都是SQL查询得到的表,我现在需要分析表中每行的记录,提取有用的信息。用fstream 的getline得到了一行string,接着理所当然地想去用string.find函数来查找空格,以此来分隔数据。然而发现find(” “)返回的结果为-1,即没有空格。然而我可没瞎,用Notepad打开文档后可是很整齐的数据表格式。此时的我内心如柯南遇到棘手的杀人案件一样,深深滴发出了疑问。。调试发现,该string中原本是空格的位置显示的是’9’,即ascii码是9,查ASCII表后发现,十进制数字9对应的是符号HT。。。HT是神马。。。敖!!!HT是TAB啊。。。我真是蠢死了~改代码为find(“\t”)后,成功对数据表进行了处理。。。
    对于这个坑,我对我的无知已经无话可说~只能说,活到老学到老,思维要放宽阔。

    3.多线程与多进程

    每次上多线程与多进程学习的课程,讲师总会俗不可耐地提出如下疑问:多线程与多进程的区别是什么?答对了有小礼品赠送哦~星星眼~原来的我对这个问题答案的理解也只是停留在了概念阶段,比如,最核心的区别,应该就是多线程可以共享同一块内存,而多进程是程序运行实例的多个副本,是独立的,并不共享数据。这种概念性的东西讲起来头头是道,可是真正在实际工作中用起来,却并不那么顺手。
    背景如下:我需要从这些数据表文件中,分析每行数据,提取每行数据的uin(类似于用户id之类的字段,约10亿量级),然后统计每个uin出现的次数。原理很简单,就是建一个hash map,然后依次读入每行数据,在每个uin对应的数值累加就可以了。然而不要忘记了这是大数据处理。如果一根筋的用一个线程(进程)依次统计这7252个文件(每个文件约百万行数据),那么估计等我被炒鱿鱼了都还没处理完。。。由于可用的服务器就一台,还是得考虑单机多核。
    有了for循环多进程的知识,单纯的我又开始写起了shell脚本,同时开16个进程,每个进程统计约454(7252/16)个文件,然后把得到的结果写到对应的文件中,最后得到的这16个文件结果再合并起来,因为中间肯定有重复的uin。行云流水的写完代码后,脚本欢快地运行了起来,然而没过多久,一些进程就被kill了。。。原来是机器内存使用超过了99%,运维GG再一次出马了。。。
    简直欲哭无泪,本来觉得很简单的一项工作实施起来备受阻碍。思考了一下,发现了问题所在:我在程序中开辟了map来做统计,如果开16个进程,相当于在这16个运行实例中都要开map,共有16个map,并且考虑最坏情况,如果我每个进程统计的这454个文件中涵盖了所有的uin,那么我的每个进程都有一份这10亿uin的hash表,16个进程就是16个10亿份。。。系统内存不崩我都要崩了。。再次被自己的智商蠢哭。。。
    所以说,为了只维护一份hash表,我应该实现的是多线程,而非多进程。。。这样,我每个线程中统计不同的文件,但所有线程共同维护一份map,统计的结果也只有一份。想通这一点后,开始改程序。linux下实现多线程的最便捷的方法就是pthread,只需如下几步

    #include<pthread.h>
    #include<unistd.h>
    
    void *thread(void *arg)
    {
     //do sth.
     return 0 ;
    }
    
    int main()
    {
        pthread_t id ;
        int ret = pthread_create(&id, NULL, thread, NULL);
        if(ret) {
            cout << "Create pthread error!" << endl;
            return 1;
        }
        //do sth.
        pthread_join(id, NULL);
        return 0;
    }
    

    其中,pthread_join很重要,他会在父进程中一直等到直到子进程结束后才退出,否则,不等子进程执行完,父进程就return了。
    这个坑让我意识到,掌握多线程和多进程开发很有必要,而且很多概念应该要学会活学活用,更重要的是,在做事情之前要三思!三思!三思!

    最后,贴一些最近用到的很有用的linux系统命令
    wc -l a.txt 查看a.txt文档中的行数
    查找所有空目录
    find ./ -depth -empty -type d -type d为只找目录,不加则找出所有空目录和空文件
    find ./ -depth -empty -type d -exec rmdir -v {} \;
    统计文件夹下的文件数量
    ls -l|grep “^-“|wc -l
    统计文件夹下(包括子文件夹)的文件数量
    ls -lR|grep “^-“|wc -l
    统计文件夹下的文件夹数量
    ls -l|grep “^d”|wc -l

    df -h 查看挂载的硬盘信息
    cat /proc/meminfo 或者 free 查看内存使用情况 或 剩余可用内存

    CPU占用最多的前10个进程:
    ps auxw|head -1;ps auxw|sort -rn -k3|head -10
    内存消耗最多的前10个进程
    ps auxw|head -1;ps auxw|sort -rn -k4|head -10
    虚拟内存使用最多的前10个进程
    ps auxw|head -1;ps auxw|sort -rn -k5|head -10

    seq -f %06g 1 10 产生000001 000002 … 000010的数据序列
    seq -f %06g 1 2 10 以2为步长,产生000001 000003 000005…000009的数据序列

    展开全文
  • Shell 脚本在处理自动循环或的任务方面可节省大量的时间,且功能强大; 创建一个脚本,在使用一系列系统命令的同时,可以使用变量、条件、算术和循环快速创建脚本以完成相应的工作(这可比在命令行下一个个敲入要...

    Shell的基础

    在 Linux 下,我们通常会出于以下原因或优点而使用 Shell 脚本:

    • Shell 脚本在处理自动循环或大的任务方面可节省大量的时间,且功能强大;
    • 创建一个脚本,在使用一系列系统命令的同时,可以使用变量、条件、算术和循环快速创建脚本以完成相应的工作(这可比在命令行下一个个敲入要节省大量的时间呢);
    • Shell脚本可以在行命令中接收信息,并使用它作为另一个命令的输入。

    Shell脚本的一般格式

      Shell脚本通常不是复杂的程序,并且它是按行解释的。脚本第一行通常会以类似于 #!/bin/bash 开始,这段脚本用于通知 Shell 使用系统上的 Bourne Shell 解释器。
      为什么说“类似于”呢?因为,实际上我们不仅可以使用 bash 解释器,还可以使用其他一些解释器,甚至是以命令开头,后面紧跟其参数。例如:

    #!/usr/bin/awk
    #!/bin/sed

    Shell命令的通配符

      星号“ * ”可以匹配文件名中的任何字符串。例如我们给出文件名模式 file*,它的意思是文件名以 file 开头,后面可以跟随任何字符串,包括空字符串。

    $ ls file*
    file    file1   file2   file3   file_test

      注意:在通配符里,一个星号“ * ”可以代表0个或多个任意字符。

      问号“ ? ”可以匹配任何单个字符。例如我们给出文件名模式 file?,它的意思是文件名以 file 开头,以任意1个字符结尾的文件:

    $ ls file?
    file1   file2   file3

      注意:一个问号“ ? ”要匹配1个任意字符。

      方括号“ [ ] ”可以匹配任意单个指定的字符。下面的例子将列出文件名以 file 开头,以任意1个数字结尾的文件:

    $ ls file[0-9]
    file1  file2  file3

      方括号“ [! ] ”可以匹配任意除指定的字符之外的单个字符。下面的例子中将列出文件名以 file 开头,不以数字结尾的文件:

    $ ls file*[!0-9]
    file_test

    Shell命令的输入和输出

      在 Shell 脚本中,可以用几种不同的方式读入数据,可以使用标准输入(缺省为键盘),或者指定一个文件作为输入。
      对于输出也一样,如果不指定某个文件作为输出,标准输出总是和终端屏幕相关联。如果所使用的命令出现了什么错误,它也会缺省输出到屏幕上,如果不想把这些信息输出到屏幕上,也可以把这些信息指定到一个文件中。
      使用 echo 命令可以显示文本行或变量,或者把字符串输入到文件。它的一般形式如下:

    echo string

      echo 命令支持转义字符,比如:

    • 不换行:“ \c ”
    • 跳格:“ \t ”
    • 换行:“ \n ”
    echo -e "hello\tworld\n"

      可以使用 read 语句从键盘或文件的某一行文本中读入信息,并将其赋给一个变量。如果只指定了一个变量,那么 read 将会把所有的输入赋给该变量,直至遇到第一个文件结束符或回车。它的一般形式如下:

    read var1 var2 ... ...

      通常,我们希望在读取输入的同时给出一些提示信息:

    read -p "Please input your name: " name

    UNIX从来都不是为人机交互而设计的,而是为程序之间的交互而设计的。

      上面这句话是 Unix 的一个设计哲学,我们通常也会想到 Unix 的另一个设计哲学——一个程序只做好一件事。
      好啦,说那么多,其实都是为了引出“管道”的概念。在 Unix 中,程序可以被看成是过滤器,程序之间的交互就是输入和输出。Unix 从很早以前就提供了管道机制,使得一个程序的输出可以通过一根管子(管道)与另一个程序的输入联系起来。
      管道在 Shell 中被广泛使用,可以用竖杠“ | ”表示。它的一般形式如下:

    命令1 | 命令2

      表示把命令1的输出通过管道传递给命令2作为输入。
      举个栗子:我们先执行命令 ls,列出当前文件名,然后将结果送入管道中,进而 wc 从管道读出这些信息,并计算总共有几个单词:

    $ ls | wc -w

      下面再来扯一下标准输入、标准输出和错误输出,每个程序开始运行时都会默认打开这三个文件,其文件描述符(fd)分别为0、1、2。

    • 标准输入(stdin)的文件描述符为0,它是命令的输入,缺省是键盘,也可以是文件或其他命令的输出;
    • 标准输出(stdout)的文件描述符为1,它是命令的输出,缺省是屏幕,也可以是文件;
    • 标准错误(stderr)的文件描述符为2,它是命令的错误输出,缺省是屏幕,同样也可以是文件。

    那我们怎么重新指定命令的标准输入、标准输出和错误输出呢?要实现这一点就需要使用文件重定向

      在对标准错误进行重定向时,必须要使用文件描述符,但是对于标准输入和输出来说,这不是 necessary。比如:
      把标准输出重定向到一个新文件:command 1 > file 或者 command > file
      把标准错误重定向到一个新文件:command 2 > file
      以 file 文件作为标准输入:command 0 < file
      如果希望以追加的方式重定向到一个文件,则把“ > ”替换为“ >> ”。

    Shell命令的执行顺序

      在执行某个命令的时候,有时需要依赖于前一个命令是否执行成功。例如,你希望将一个目录中的文件全部拷贝到另一个目录中后,再删除源目录中的全部文件。因此,再删除之前,你希望能够确保拷贝成功,否则就可能丢失所有的文件。
      那么,你可以这么做:

    cp -rf dir1/* dir2/* && rm -rf dir1/*

      使用“ && ”的一般形式为:

    命令1 && 命令2

      这种命令执行方式相当地直接:&& 左边的命令(命令1)返回真(即返回0,成功被执行)后,&& 右边的命令(命令2)才能够被执行。
      换句话说,使用 && 的意思就是:

    如果这个命令(命令1)执行成功 && 那么执行这个命令(命令2)

      相应的还有“ || ”,它的一般形式为:

    命令1 || 命令2

      与 && 相反,|| 的作用是:如果 || 左边的命令(命令1)未执行成功,那么就执行 || 右边的命令(命令2)。
      换句话说,使用 || 的意思就是:

    如果这个命令(命令1)执行失败了 || 那么就执行这个命令(命令2)

      举个例子:我们希望从一个审计文件中抽取第1个和第5个域,并将其输出到一个临时文件中,如果这一操作未能成功,我们希望能够看到错误提示。

    awk '{print $1 $5}' acc > a.tmp || echo "operation failed"

    grep命令

      grep 是 Unix/Linux 中使用最广泛的命令之一。grep(Globally search a Regular Expression and Print)是一个强大的文本搜索工具,它能使用特定模式匹配搜索文本,并默认输出匹配行。grep 支持基本正则表达式,也支持其扩展集,Unix 的 grep 家族还包括 egrep 和 fgrep。
      grep 一般格式为:

    grep [选项] 基本正则表达式 [文件]

      这里基本正则表达式可为字符串。在 grep 命令中输入字符串参数时,最好将其用双引号括起来,这样做有两个好处:一是以防被误解为 shell 命令,二是可以用来查找多个单词组成的字符串。

      以下是一些常用的 grep 选项:

    选项 描述
    -c 只输出匹配行的计数
    -i 不区分大小写(只适用于单字符)
    -h 查询多文件时不显示文件名
    -l 查询多文件时只输出包含匹配字符的文件名
    -n 显示匹配行及行号
    -s 不显示不存在或无匹配文本的错误信息
    -v 显示不包含匹配文本的所有行

    正则表达式

      随着对 Unix 和 Linux 熟悉程度的不断加深,需要经常接触到正则表达式这个领域。
      使用 shell 时,从一个文件中抽取多于一个字符串将会很麻烦。例如:在一个文本中抽取一个词,它的头两个字符是大写的,后面紧跟四个数字。如果不使用某种正则表达式,在 shell 中将无法实现这个操作。
      当从一个文件或命令输出中抽取或过滤文本时,可以使用正则表达式(RE),正则表达式是一些特殊或不很特殊的字符串模式的集合。
      为了抽取或获得信息,我们给出抽取操作应遵守的一些规则。这些规则由一些特殊字符或进行模式匹配操作时使用的元字符组成。

    选项 描述
    ^ 只匹配行首
    $ 只匹配行尾
    * 一个单字符后紧跟 *,匹配0个或多个此单字符
    [] 匹配 [] 内字符,可以是一个单字符,也可以是字符序列
    \ 用来屏蔽一个元字符的特殊含义
    . 匹配任意单字符
    pattern\{n\} 用来匹配前面 pattern 出现次数,n 为次数
    pattern\{n, \} 含义同上,但次数最少为 n
    pattern\{n, m\} 含义同上,但 pattern 出现次数在 n 与 m 之间

    上古神器:awk 与 sed

    awk命令

    有3种方法调用 awk
    (1)命令行方式:

    awk [-F 分隔符] 'awk命令' <待处理文件>

      注意:[-F 分隔符] 是可选的,awk 使用空格作为缺省的分隔符。

    (2)将所有 awk 命令插入一个单独的文件,然后调用:

    awk -f '包含awk命令的文件' 待处理文件

    (3)将所有 awk 命令插入一个文件,并使 awk 程序可执行,然后用 awk 命令解释器作为脚本的首行,以便通过键入脚本名称来调用它。

    awk 的模式和动作

      任何 awk 语句都由模式和动作组成。在一个 awk 脚本中可能有许多语句。模式部分决定动作语句何时触发及触发事件。处理即对数据进行的操作,如果省略模式部分,动作将时刻保持执行状态。
      实际动作在大括号 { } 内指明。动作大多数用来打印,但是还有些更长的代码诸如 if 和循环语句及循环退出结构。如果不指明采取动作,awk将打印出所有浏览出来的记录。

    awk 的域和记录

      awk 执行时,其浏览域标记为 $1, $2, ..., $n ,这种方法称为域标识。使用这些域标识将更容易对域进行更进一步的处理。
      例如,如下命令的作用是打印文件 file 中的第一个域:

    awk '{print $1}' file

      (注意:上面示例中没有模式,只有动作,即 { } 里面的语句。)

    awk 的条件操作符示例

    操作符 含义 命令 描述
    < 小于 awk '$7<30 {print $0}' file $7小于30的行打印出来
    <= 小于等于 awk '$7<=30 {print $0}' file $7小于等于30的行打印出来
    == 等于 awk '$7==30 {print $0}' file $7等于30的行打印出来
    != 不等于 awk '$7!=30 {print $0}' file $7不等于30的行打印出来
    >= 大于等于 awk '$7>=30 {print $0}' file $7大于等于30的行打印出来
    ~ 匹配 awk '$0~/48/ {print $0}' file 将能匹配48的行打印出来
    !~ 不匹配 awk '$0!~/48/ {print $0}' file 将不能匹配48的行打印出来

    sed命令

      sed 是一个非交互性文本流编辑器,它编辑文件或标准输入导出的文本拷贝。标准输入可能是来自键盘,文件重定向,字符串或变量,又或者是一个管道的文本。
      使用 sed 需要记住的一个重要事实是:无论命令是什么,sed 并不与初始化文件打交道,它操作的只是一个拷贝,然后所有的改动如果没有重定向到一个文件,将输出到屏幕。
      跟 grep 和 awk 一样,sed 是一个重要的文本过滤工具,或者使用一行命令或者使用管道与 grep 和 awk 相结合。
      调用 sed 有三种方式:
      (1)在命令行键入命令

    sed [选项] 'sed命令' 输入文件

      注意:在命令行使用 sed 命令时,实际命令要加单引号,sed 也允许加双引号。

      (2)将 sed 命令插入脚本文件,然后调用 sed

    sed [选项] -f sed脚本文件 输入文件 

      (3)将 sed 命令插入脚本文件,并使 sed 脚本可执行

    sed脚本文件 [选项] 输入文件

    Shell脚本控制

    结构化命令

      使用 if-then 语句,语法如下:

    if [ command ]; then
        other commands
    fi

    或者:

    if [ command ]; then
        other commands
    else
        other commands
    fi

      注意:当 command 退出码为0时(即正常退出),执行 if 语句,否则执行 else 语句。
      在 shell 编程中,我们常常需要判断各种条件,以便执行不同路径。test 命令提供了在 if-then 语句中测试不同条件的途径,如果 test 命令中列出的条件成立,那么 test 命令将会退出且返回0。格式如下:

    test condition

      Bash 提供了另一种在 if-then 语句中使用 test 的方法:

    if [ confition ]; then
        ...
    fi

      其中的条件判断可分为3类:
      (1)数值比较

    比较 描述
    n1 -eq n2 检查n1是否等于n2
    n1 -ge n2 检查n1是否大于或等于n2
    n1 -gt n2 检查n1是否大于n2
    n1 -le n2 检查n1是否小于或等于n2
    n1 -lt n2 检查n1是否小于n2
    n1 -ne n2 检查n1是否不等于n2

      (2)字符串比较

    比较 描述
    str1 = str2 检查str1与str2是否相同
    str1 != str2 检查str1与str2是否不同
    str1 < str2 检查str1是否小于str2
    str1 > str2 检查str1是否大于str2
    -n str 检查str的长度是否为非0
    -z str 检查str的长度是否为0

      (3)文件比较

    比较 描述
    -d file 检查file是否存在且是一个目录
    -e file 检查file是否存在
    -f file 检查file是否存在且是一个普通文件
    -r file 检查file是否存在且可读
    -s file 检查file是否存在且非空
    -w file 检查file是否存在且可写
    -x file 检查file是否存在且可执行

      虽然 if-then 语句可以胜任条件判断/分支执行的工作,但是如果分支过多,则会导致代码臃肿。因此,可以使用 case 语句来替代,避免过长的 if-then 语句。shell 中的 case 语句类似于 C 语言的 switch 语句,一般形式如下:

    case varible in
        pattern1 | pattern2) command1;;
        pattern3) command2;;
        *) command3;;
    esac

      Shell 同样支持循环语句,包括 for 命令和 while 命令。
      其中 for 循环格式如下:

    for varible in list
    do
        commands
    done

      在 list 参数中,需要提供迭代中一系列要使用的值,在每个迭代中,varible 会包含列表中的当前值,一次使用一个值,以此类推。
      while 语句可以看成是 if-then 语句和 for 循环的混合。while 语句允许你定义一个要测试的命令,如果测试命令返回的退出状态码是0,则循环执行一组命令。格式如下:

    while test command
    do
        other commands
    done

    用户输入

      Shell 脚本允许你在运行它的同时给它传递参数,例如:

    ./somescript.sh abcd 100

      这两个参数,在脚本里面可以使用 $1$2 来获取,而 $0 代表的是脚本名字本身。
      需要注意的一个细节是:当命令行参数超过 9 个,比如第 10 个参数,引用的时候必须使用花括号括起来,例如:${10},这种技术使得可以向脚本添加任意多个参数。
      此外,我们还应该记住下面这些特殊的参数变量。
      $# 特殊变量代表脚本运行时带有的命令行参数个数(不包含脚本名在内),对于上面的命令,$# 的值为2。
      这样的话,如果我们想知道最后一个参数的值,就可以利用这个特殊变量,而不需要知道总共有多少个参数。噔!噔!噔!—— ${$#}
      $*$@ 变量的含义是相同的,它们会将命令行上提供的所有参数当作同一个字符串中的多个独立的单词。这样就可以使用 for 来遍历所有的值:

    #!/bin/bash
    
    count=1
    for param in $*
    do
        echo "\$* : #$count = $param"
        count=$(($count + 1))
    done

      我们输入 ./test.sh a b c ,其输出结果如下:

    $* : #1 = a
    $* : #2 = b
    $* : #3 = c

    脚本函数

      函数的定义格式如下:

    function_name()
    {
        commands
    }

    需要注意的是:

    • 函数没有返回值(事实上,所有的值都是字符串);
    • 函数名后面有一对圆括号,括号里面为空;
    • 函数的定义必须要在函数的调用之前;

    虽然函数的定义中没有出现参数列表,但是在调用函数的时候,依然可以传参,像这样:

    function_name 12 34

      这样的话,在函数定义内部,我们就可以使用 $1$2 等等来表示传递过来的参数里。类似的,$0 表示函数本身的名字。


    展开全文
  • Shell简介:什么是ShellShell命令的两种执行方式 Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。...

    Shell简介:什么是ShellShell命令的两种执行方式

    Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

    它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Unix/Linux系统的关键。

    可以说,shell使用的熟练程度反映了用户对Unix/Linux使用的熟练程度。

    注意:单独地学习 Shell 是没有意义的,请先参考Unix/Linux入门教程,了解 Unix/Linux 基础。

    Shell有两种执行命令的方式:

    • 交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。
    • 批处理(Batch):用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。


    Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。

    Shell初学者请注意,在平常应用中,建议不要用 root 帐号运行 Shell 。作为普通用户,不管您有意还是无意,都无法破坏系统;但如果是 root,那就不同了,只要敲几个字母,就可能导致灾难性后果。

    几种常见的Shell

     

    上面提到过,Shell是一种脚本语言,那么,就必须有解释器来执行这些脚本。

    Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等,习惯上把它们称作一种Shell。我们常说有多少种Shell,其实说的是Shell脚本解释器。

    bash

    bash是Linux标准默认的shell,本教程也基于bash讲解。bash由Brian Fox和Chet Ramey共同完成,是BourneAgainShell的缩写,内部命令一共有40个。

    Linux使用它作为默认的shell是因为它有诸如以下的特色:

    • 可以使用类似DOS下面的doskey的功能,用方向键查阅和快速输入并修改命令。
    • 自动通过查找匹配的方式给出以某字符串开头的命令。
    • 包含了自身的帮助功能,你只要在提示符下面键入help就可以得到相关的帮助。

    sh

    sh 由Steve Bourne开发,是BourneShell的缩写,sh 是Unix 标准默认的shell。

    ash

    ash shell 是由Kenneth Almquist编写的,Linux中占用系统资源最少的一个小shell,它只包含24个内部命令,因而使用起来很不方便。

    csh

    csh 是Linux比较大的内核,它由以WilliamJoy为代表的共计47位作者编成,共有52个内部命令。该shell其实是指向/bin/tcsh这样的一个shell,也就是说,csh其实就是tcsh。

    ksh

    ksh 是Korn shell的缩写,由EricGisin编写,共有42条内部命令。该shell最大的优点是几乎和商业发行版的ksh完全兼容,这样就可以在不用花钱购买商业版本的情况下尝试商业版本的性能了。

    注意:bash是 Bourne Again Shell 的缩写,是linux标准的默认shell ,它基于Bourne shell,吸收了C shell和Korn shell的一些特性。bash完全兼容sh,也就是说,用sh写的脚本可以不加修改的在bash中执行。

     

    Shell脚本语言与编译型语言的差异

    大体上,可以将程序设计语言可以分为两类:编译型语言和解释型语言。

    编译型语言

    很多传统的程序设计语言,例如Fortran、Ada、Pascal、C、C++和Java,都是编译型语言。这类语言需要预先将我们写好的源代码(source code)转换成目标代码(object code),这个过程被称作“编译”。

    运行程序时,直接读取目标代码(object code)。由于编译后的目标代码(object code)非常接近计算机底层,因此执行效率很高,这是编译型语言的优点。

    但是,由于编译型语言多半运作于底层,所处理的是字节、整数、浮点数或是其他机器层级的对象,往往实现一个简单的功能需要大量复杂的代码。例如,在C++里,就很难进行“将一个目录里所有的文件复制到另一个目录中”之类的简单操作。

    解释型语言

    解释型语言也被称作“脚本语言”。执行这类程序时,解释器(interpreter)需要读取我们编写的源代码(source code),并将其转换成目标代码(object code),再由计算机运行。因为每次执行程序都多了编译的过程,因此效率有所下降。

    使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象;缺点是它们的效率通常不如编译型语言。不过权衡之下,通常使用脚本编程还是值得的:花一个小时写成的简单脚本,同样的功能用C或C++来编写实现,可能需要两天,而且一般来说,脚本执行的速度已经够快了,快到足以让人忽略它性能上的问题。脚本编程语言的例子有awk、Perl、Python、Ruby与Shell。

     

    什么时候使用Shell

    因为Shell似乎是各UNIX系统之间通用的功能,并且经过了POSIX的标准化。因此,Shell脚本只要“用心写”一次,即可应用到很多系统上。因此,之所以要使用Shell脚本是基于:

    • 简单性:Shell是一个高级语言;通过它,你可以简洁地表达复杂的操作。
    • 可移植性:使用POSIX所定义的功能,可以做到脚本无须修改就可在不同的系统上执行。
    • 开发容易:可以在短时间内完成一个功能强大又妤用的脚本。


    但是,考虑到Shell脚本的命令限制和效率问题,下列情况一般不使用Shell:

    1. 资源密集型的任务,尤其在需要考虑效率时(比如,排序,hash等等)。
    2. 需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用C++或FORTRAN 来处理)。
    3. 有跨平台(操作系统)移植需求(一般使用C 或Java)。
    4. 复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)。
    5. 对于影响系统全局性的关键任务应用。
    6. 对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵、破解、恶意破坏等等。
    7. 项目由连串的依赖的各个部分组成。
    8. 需要大规模的文件操作。
    9. 需要多维数组的支持。
    10. 需要数据结构的支持,比如链表或数等数据结构。
    11. 需要产生或操作图形化界面 GUI。
    12. 需要直接操作系统硬件。
    13. 需要 I/O 或socket 接口。
    14. 需要使用库或者遗留下来的老代码的接口。
    15. 私人的、闭源的应用(shell 脚本把代码就放在文本文件中,全世界都能看到)。


    如果你的应用符合上边的任意一条,那么就考虑一下更强大的语言吧——或许是Perl、Tcl、Python、Ruby——或者是更高层次的编译语言比如C/C++,或者是Java。即使如此,你会发现,使用shell来原型开发你的应用,在开发步骤中也是非常有用的。

     

    第一个Shell脚本

     

    打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好,如果你用php写shell 脚本,扩展名就用php好了。

    输入一些代码:

    1. #!/bin/bash
    2. echo "Hello World !"

    “#!” 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell。echo命令用于向窗口输出文本。

    运行Shell脚本有两种方法。

    作为可执行程序

    将上面的代码保存为test.sh,并 cd 到相应目录:

    chmod +x ./test.sh  #使脚本具有执行权限

    ./test.sh #执行脚本

    注意,一定要写成./test.sh,而不是test.sh。运行其它二进制的程序也一样,直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里,所以写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找。

    通过这种方式运行bash脚本,第一行一定要写对,好让系统查找到正确的解释器。

    这里的"系统",其实就是shell这个应用程序(想象一下Windows Explorer),但我故意写成系统,是方便理解,既然这个系统就是指shell,那么一个使用/bin/sh作为解释器的脚本是不是可以省去第一行呢?是的。

    作为解释器参数

    这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

    /bin/sh test.sh

    /bin/php test.php

    这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

    再看一个例子。下面的脚本使用 read 命令从 stdin 获取输入并赋值给 PERSON变量,最后在 stdout 上输出:

    1. #!/bin/bash
    2.  
    3. # Author : mozhiyan
    4. # Copyright (c) http://see.xidian.edu.cn/cpp/linux/
    5. # Script follows here:
    6.  
    7. echo "What is your name?"
    8. read PERSON
    9. echo "Hello, $PERSON"

    运行脚本:

    chmod +x ./test.sh

    $./test.sh

    What is your name?

    mozhiyan

    Hello, mozhiyan

    $

     

    Shell变量:Shell变量的定义、删除变量、只读变量、变量类型

     

    Shell支持自定义变量。

    定义变量

    定义变量时,变量名不加美元符号($),如:

    1. variableName="value"

    注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

    • 首个字符必须为字母(a-z,A-Z)。
    • 中间不能有空格,可以使用下划线(_)。
    • 不能使用标点符号。
    • 不能使用bash里的关键字(可用help命令查看保留关键字)。


    变量定义举例:

    1. myUrl="http://see.xidian.edu.cn/cpp/linux/"
    2. myNum=100

    使用变量

    使用一个定义过的变量,只要在变量名前面加美元符号($)即可,如:

    1. your_name="mozhiyan"
    2. echo $your_name
    3. echo ${your_name}

    变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

    1. for skill in Ada Coffe Action Java
    2. do
    3.     echo "I am good at ${skill}Script"
    4. done

    如果不给skill变量加花括号,写成echo "I am good at $skillScript",解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

    推荐给所有变量加上花括号,这是个好的编程习惯。

    重新定义变量

    已定义的变量,可以被重新定义,如:

    1. myUrl="http://see.xidian.edu.cn/cpp/linux/"
    2. echo ${myUrl}
    3.  
    4. myUrl="http://see.xidian.edu.cn/cpp/shell/"
    5. echo ${myUrl}

    这样写是合法的,但注意,第二次赋值的时候不能写 $myUrl="http://see.xidian.edu.cn/cpp/shell/",使用变量的时候才加美元符($)。

    只读变量

    使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

    下面的例子尝试更改只读变量,结果报错:

    1. #!/bin/bash
    2.  
    3. myUrl="http://see.xidian.edu.cn/cpp/shell/"
    4. readonly myUrl
    5. myUrl="http://see.xidian.edu.cn/cpp/danpianji/"

    运行脚本,结果如下:

    /bin/sh: NAME: This variable is read only.

    删除变量

    使用 unset 命令可以删除变量。语法:

    1. unset variable_name

    变量被删除后不能再次使用;unset 命令不能删除只读变量。

    举个例子:

    1. #!/bin/sh
    2.  
    3. myUrl="http://see.xidian.edu.cn/cpp/u/xitong/"
    4. unset myUrl
    5. echo $myUrl

    上面的脚本没有任何输出。

    变量类型

    运行shell时,会同时存在三种变量:

    1) 局部变量

    局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

    2) 环境变量

    所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

    3) shell变量

    shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

     

    Shell特殊变量:Shell $0, $#, $*, $@, $?, $$和命令行参数

     

    前面已经讲到,变量名只能包含数字、字母和下划线,因为某些包含其他字符的变量有特殊含义,这样的变量被称为特殊变量。

    例如,$ 表示当前Shell进程的ID,即pid,看下面的代码:

    1. $echo $$

    运行结果

    29949

     

    特殊变量列表

    变量

    含义

    $0

    当前脚本的文件名

    $n

    传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。

    $#

    传递给脚本或函数的参数个数。

    $*

    传递给脚本或函数的所有参数。

    $@

    传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。

    $?

    上个命令的退出状态,或函数的返回值。

    $$

    当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

    命令行参数

    运行脚本时传递给脚本的参数称为命令行参数。命令行参数用 $n 表示,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推。

    请看下面的脚本:

    1. #!/bin/bash
    2.  
    3. echo "File Name: $0"
    4. echo "First Parameter : $1"
    5. echo "First Parameter : $2"
    6. echo "Quoted Values: $@"
    7. echo "Quoted Values: $*"
    8. echo "Total Number of Parameters : $#"

    运行结果:

    $./test.sh Zara Ali

    File Name : ./test.sh

    First Parameter : Zara

    Second Parameter : Ali

    Quoted Values: Zara Ali

    Quoted Values: Zara Ali

    Total Number of Parameters : 2

    $* 和 $@ 的区别

    $* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。

    但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。

    下面的例子可以清楚的看到 $* 和 $@ 的区别:

    1. #!/bin/bash
    2. echo "\$*=" $*
    3. echo "\"\$*\"=" "$*"
    4.  
    5. echo "\$@=" $@
    6. echo "\"\$@\"=" "$@"
    7.  
    8. echo "print each param from \$*"
    9. for var in $*
    10. do
    11.     echo "$var"
    12. done
    13.  
    14. echo "print each param from \$@"
    15. for var in $@
    16. do
    17.     echo "$var"
    18. done
    19.  
    20. echo "print each param from \"\$*\""
    21. for var in "$*"
    22. do
    23.     echo "$var"
    24. done
    25.  
    26. echo "print each param from \"\$@\""
    27. for var in "$@"
    28. do
    29.     echo "$var"
    30. done

    执行 ./test.sh "a" "b" "c""d",看到下面的结果:

    $*= a b c d

    "$*"= a b c d

    $@= a b c d

    "$@"= a b c d

    print each param from $*

    a

    b

    c

    d

    print each param from $@

    a

    b

    c

    d

    print each param from "$*"

    a b c d

    print each param from "$@"

    a

    b

    c

    d

    退出状态

    $? 可以获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回结果。

    退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1。

    不过,也有一些命令返回其他值,表示不同类型的错误。

    下面例子中,命令成功执行:

    $./test.sh Zara Ali

    File Name : ./test.sh

    First Parameter : Zara

    Second Parameter : Ali

    Quoted Values: Zara Ali

    Quoted Values: Zara Ali

    Total Number of Parameters : 2

    $echo $?

    0

    $


    $? 也可以表示函数的返回值,后续将会讲解。

     

    Shell替换:Shell变量替换,命令替换,转义字符

     

    如果表达式中包含特殊字符,Shell 将会进行替换。例如,在双引号中使用变量就是一种替换,转义字符也是一种替换。

    举个例子:

    1. #!/bin/bash
    2.  
    3. a=10
    4. echo -e "Value of a is $a \n"

    运行结果:

    Value of a is 10

    这里 -e 表示对转义字符进行替换。如果不使用 -e 选项,将会原样输出:

    Value of a is 10\n


    下面的转义字符都可以用在 echo 中:

    转义字符

    含义

    \\

    反斜杠

    \a

    警报,响铃

    \b

    退格(删除键)

    \f

    换页(FF),将当前位置移到下页开头

    \n

    换行

    \r

    回车

    \t

    水平制表符(tab键) 

    \v

    垂直制表符

    可以使用 echo 命令的 -E 选项禁止转义,默认也是不转义的;使用 -n 选项可以禁止插入换行符。

    命令替换

    命令替换是指Shell可以先执行命令,将输出结果暂时保存,在适当的地方输出。

    命令替换的语法:

    1. `command`

    注意是反引号,不是单引号,这个键位于 Esc 键下方。

    下面的例子中,将命令执行结果保存在变量中:

    1. #!/bin/bash
    2.  
    3. DATE=`date`
    4. echo "Date is $DATE"
    5.  
    6. USERS=`who | wc -l`
    7. echo "Logged in user are $USERS"
    8.  
    9. UP=`date ; uptime`
    10. echo "Uptime is $UP"

    运行结果:

    Date is Thu Jul  2 03:59:57 MST 2009

    Logged in user are 1

    Uptime is Thu Jul  2 03:59:57 MST 2009

    03:59:57 up 20 days, 14:03,  1 user, load avg: 0.13, 0.07, 0.15

    变量替换

    变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值

    可以使用的变量替换形式:

    形式

    说明

    ${var}

    变量本来的值

    ${var:-word}

    如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值。

    ${var:=word}

    如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。

    ${var:?message}

    如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。
    若此替换出现在Shell脚本中,那么脚本将停止运行。

    ${var:+word}

    如果变量 var 被定义,那么返回 word,但不改变 var 的值。


    请看下面的例子:

    #!/bin/bash

     

    echo ${var:-"Variable is notset"}

    echo "1 - Value of var is ${var}"

     

    echo ${var:="Variable is notset"}

    echo "2 - Value of var is ${var}"

     

    unset var

    echo ${var:+"This is defaultvalue"}

    echo "3 - Value of var is $var"

     

    var="Prefix"

    echo ${var:+"This is defaultvalue"}

    echo "4 - Value of var is $var"

     

    echo ${var:?"Print this message"}

    echo "5 - Value of var is ${var}"

    运行结果:

    1. Variable is not set
    2. 1 - Value of var is
    3. Variable is not set
    4. 2 - Value of var is Variable is not set
    5.  
    6. 3 - Value of var is
    7. This is default value
    8. 4 - Value of var is Prefix
    9. Prefix
    10. 5 - Value of var is Prefix

     

    Shell运算符:Shell算数运算符、关系运算符、布尔运算符、字符串运算符等

     

    Bash 支持很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试运算符。

    原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。

    expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

    例如,两个数相加:

    1. #!/bin/bash
    2.  
    3. val=`expr 2 + 2`
    4. echo "Total value : $val"

    运行脚本输出:

    Total value : 4

    两点注意:

    • 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
    • 完整的表达式要被 ` ` 包含,注意这个字符不是常用的单引号,在 Esc 键下边。

    算术运算符

    先来看一个使用算术运算符的例子:

    1. #!/bin/sh
    2.  
    3. a=10
    4. b=20
    5. val=`expr $a + $b`
    6. echo "a + b : $val"
    7.  
    8. val=`expr $a - $b`
    9. echo "a - b : $val"
    10.  
    11. val=`expr $a \* $b`
    12. echo "a * b : $val"
    13.  
    14. val=`expr $b / $a`
    15. echo "b / a : $val"
    16.  
    17. val=`expr $b % $a`
    18. echo "b % a : $val"
    19.  
    20. if [ $a == $b ]
    21. then
    22.    echo "a is equal to b"
    23. fi
    24.  
    25. if [ $a != $b ]
    26. then
    27.    echo "a is not equal to b"
    28. fi

    运行结果:

    a + b : 30

    a - b : -10

    a * b : 200

    b / a : 2

    b % a : 0

    a is not equal to b

    注意:

    • 乘号(*)前边必须加反斜杠(\)才能实现乘法运算;
    • if...then...fi 是条件语句,后续将会讲解。

     

    算术运算符列表

    运算符

    说明

    举例

    +

    加法

    `expr $a + $b` 结果为 30。

    -

    减法

    `expr $a - $b` 结果为 10。

    *

    乘法

    `expr $a \* $b` 结果为  200。

    /

    除法

    `expr $b / $a` 结果为 2。

    %

    取余

    `expr $b % $a` 结果为 0。

    =

    赋值

    a=$b 将把变量 b 的值赋给 a。

    ==

    相等。用于比较两个数字,相同则返回 true。

    [ $a == $b ] 返回 false。

    !=

    不相等。用于比较两个数字,不相同则返回 true。

    [ $a != $b ] 返回 true。


    注意:条件表达式要放在方括号之间,并且要有空格,例如 [$a==$b] 是错误的,必须写成 [ $a == $b ]。

    关系运算符

    关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

    先来看一个关系运算符的例子:

    1. #!/bin/sh
    2.  
    3. a=10
    4. b=20
    5. if [ $a -eq $b ]
    6. then
    7.    echo "$a -eq $b : a is equal to b"
    8. else
    9.    echo "$a -eq $b: a is not equal to b"
    10. fi
    11.  
    12. if [ $a -ne $b ]
    13. then
    14.    echo "$a -ne $b: a is not equal to b"
    15. else
    16.    echo "$a -ne $b : a is equal to b"
    17. fi
    18.  
    19. if [ $a -gt $b ]
    20. then
    21.    echo "$a -gt $b: a is greater than b"
    22. else
    23.    echo "$a -gt $b: a is not greater than b"
    24. fi
    25.  
    26. if [ $a -lt $b ]
    27. then
    28.    echo "$a -lt $b: a is less than b"
    29. else
    30.    echo "$a -lt $b: a is not less than b"
    31. fi
    32.  
    33. if [ $a -ge $b ]
    34. then
    35.    echo "$a -ge $b: a is greater or  equal to b"
    36. else
    37.    echo "$a -ge $b: a is not greater or equal to b"
    38. fi
    39.  
    40. if [ $a -le $b ]
    41. then
    42.    echo "$a -le $b: a is less or  equal to b"
    43. else
    44.    echo "$a -le $b: a is not less or equal to b"
    45. fi

    运行结果:

    10 -eq 20: a is not equal to b

    10 -ne 20: a is not equal to b

    10 -gt 20: a is not greater than b

    10 -lt 20: a is less than b

    10 -ge 20: a is not greater or equal to b

    10 -le 20: a is less or  equal to b

     

    关系运算符列表

    运算符

    说明

    举例

    -eq

    检测两个数是否相等,相等返回 true。

    [ $a -eq $b ] 返回 true。

    -ne

    检测两个数是否相等,不相等返回 true。

    [ $a -ne $b ] 返回 true。

    -gt

    检测左边的数是否大于右边的,如果是,则返回 true。

    [ $a -gt $b ] 返回 false。

    -lt

    检测左边的数是否小于右边的,如果是,则返回 true。

    [ $a -lt $b ] 返回 true。

    -ge

    检测左边的数是否大等于右边的,如果是,则返回 true。

    [ $a -ge $b ] 返回 false。

    -le

    检测左边的数是否小于等于右边的,如果是,则返回 true。

    [ $a -le $b ] 返回 true。

    布尔运算符

    先来看一个布尔运算符的例子:

    1. #!/bin/sh
    2.  
    3. a=10
    4. b=20
    5.  
    6. if [ $a != $b ]
    7. then
    8.    echo "$a != $b : a is not equal to b"
    9. else
    10.    echo "$a != $b: a is equal to b"
    11. fi
    12.  
    13. if [ $a -lt 100 -a $b -gt 15 ]
    14. then
    15.    echo "$a -lt 100 -a $b -gt 15 : returns true"
    16. else
    17.    echo "$a -lt 100 -a $b -gt 15 : returns false"
    18. fi
    19.  
    20. if [ $a -lt 100 -o $b -gt 100 ]
    21. then
    22.    echo "$a -lt 100 -o $b -gt 100 : returns true"
    23. else
    24.    echo "$a -lt 100 -o $b -gt 100 : returns false"
    25. fi
    26.  
    27. if [ $a -lt 5 -o $b -gt 100 ]
    28. then
    29.    echo "$a -lt 100 -o $b -gt 100 : returns true"
    30. else
    31.    echo "$a -lt 100 -o $b -gt 100 : returns false"
    32. fi

    运行结果:

    10 != 20 : a is not equal to b

    10 -lt 100 -a 20 -gt 15 : returns true

    10 -lt 100 -o 20 -gt 100 : returns true

    10 -lt 5 -o 20 -gt 100 : returns false

     

    布尔运算符列表

    运算符

    说明

    举例

    !

    非运算,表达式为 true 则返回 false,否则返回 true。

    [ ! false ] 返回 true。

    -o

    或运算,有一个表达式为 true 则返回 true。

    [ $a -lt 20 -o $b -gt 100 ] 返回 true。

    -a

    与运算,两个表达式都为 true 才返回 true。

    [ $a -lt 20 -a $b -gt 100 ] 返回 false。

    字符串运算符

    先来看一个例子:

    1. #!/bin/sh
    2.  
    3. a="abc"
    4. b="efg"
    5.  
    6. if [ $a = $b ]
    7. then
    8.    echo "$a = $b : a is equal to b"
    9. else
    10.    echo "$a = $b: a is not equal to b"
    11. fi
    12.  
    13. if [ $a != $b ]
    14. then
    15.    echo "$a != $b : a is not equal to b"
    16. else
    17.    echo "$a != $b: a is equal to b"
    18. fi
    19.  
    20. if [ -z $a ]
    21. then
    22.    echo "-z $a : string length is zero"
    23. else
    24.    echo "-z $a : string length is not zero"
    25. fi
    26.  
    27. if [ -n $a ]
    28. then
    29.    echo "-n $a : string length is not zero"
    30. else
    31.    echo "-n $a : string length is zero"
    32. fi
    33.  
    34. if [ $a ]
    35. then
    36.    echo "$a : string is not empty"
    37. else
    38.    echo "$a : string is empty"
    39. fi

    运行结果:

    abc = efg: a is not equal to b

    abc != efg : a is not equal to b

    -z abc : string length is not zero

    -n abc : string length is not zero

    abc : string is not empty

     

    字符串运算符列表

    运算符

    说明

    举例

    =

    检测两个字符串是否相等,相等返回 true。

    [ $a = $b ] 返回 false。

    !=

    检测两个字符串是否相等,不相等返回 true。

    [ $a != $b ] 返回 true。

    -z

    检测字符串长度是否为0,为0返回 true。

    [ -z $a ] 返回 false。

    -n

    检测字符串长度是否为0,不为0返回 true。

    [ -z $a ] 返回 true。

    str

    检测字符串是否为空,不为空返回 true。

    [ $a ] 返回 true。

    文件测试运算符

    文件测试运算符用于检测 Unix 文件的各种属性。

    例如,变量 file 表示文件“/var/www/tutorialspoint/unix/test.sh”,它的大小为100字节,具有 rwx 权限。下面的代码,将检测该文件的各种属性:

    1. #!/bin/sh
    2.  
    3. file="/var/www/tutorialspoint/unix/test.sh"
    4.  
    5. if [ -r $file ]
    6. then
    7.    echo "File has read access"
    8. else
    9.    echo "File does not have read access"
    10. fi
    11.  
    12. if [ -w $file ]
    13. then
    14.    echo "File has write permission"
    15. else
    16.    echo "File does not have write permission"
    17. fi
    18.  
    19. if [ -x $file ]
    20. then
    21.    echo "File has execute permission"
    22. else
    23.    echo "File does not have execute permission"
    24. fi
    25.  
    26. if [ -f $file ]
    27. then
    28.    echo "File is an ordinary file"
    29. else
    30.    echo "This is sepcial file"
    31. fi
    32.  
    33. if [ -d $file ]
    34. then
    35.    echo "File is a directory"
    36. else
    37.    echo "This is not a directory"
    38. fi
    39.  
    40. if [ -s $file ]
    41. then
    42.    echo "File size is zero"
    43. else
    44.    echo "File size is not zero"
    45. fi
    46.  
    47. if [ -e $file ]
    48. then
    49.    echo "File exists"
    50. else
    51.    echo "File does not exist"
    52. fi

    运行结果:

    File has read access

    File has write permission

    File has execute permission

    File is an ordinary file

    This is not a directory

    File size is zero

    File exists

     

    文件测试运算符列表

    操作符

    说明

    举例

    -b file

    检测文件是否是块设备文件,如果是,则返回 true。

    [ -b $file ] 返回 false。

    -c file

    检测文件是否是字符设备文件,如果是,则返回 true。

    [ -b $file ] 返回 false。

    -d file

    检测文件是否是目录,如果是,则返回 true。

    [ -d $file ] 返回 false。

    -f file

    检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。

    [ -f $file ] 返回 true。

    -g file

    检测文件是否设置了 SGID 位,如果是,则返回 true。

    [ -g $file ] 返回 false。

    -k file

    检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。

    [ -k $file ] 返回 false。

    -p file

    检测文件是否是具名管道,如果是,则返回 true。

    [ -p $file ] 返回 false。

    -u file

    检测文件是否设置了 SUID 位,如果是,则返回 true。

    [ -u $file ] 返回 false。

    -r file

    检测文件是否可读,如果是,则返回 true。

    [ -r $file ] 返回 true。

    -w file

    检测文件是否可写,如果是,则返回 true。

    [ -w $file ] 返回 true。

    -x file

    检测文件是否可执行,如果是,则返回 true。

    [ -x $file ] 返回 true。

    -s file

    检测文件是否为空(文件大小是否大于0),不为空返回 true。

    [ -s $file ] 返回 true。

    -e file

    检测文件(包括目录)是否存在,如果是,则返回 true。

     

    Shell注释

     

    以“#”开头的行就是注释,会被解释器忽略。

    sh里没有多行注释,只能每一行加一个#号。只能像这样:

    1. #--------------------------------------------
    2. # 这是一个自动打ipa的脚本,基于webfrogs的ipa-build书写:
    3. # https://github.com/webfrogs/xcode_shell/blob/master/ipa-build
    4.  
    5. # 功能:自动为etao ios app打包,产出物为14个渠道的ipa包
    6. # 特色:全自动打包,不需要输入任何参数
    7. #--------------------------------------------
    8.  
    9. ##### 用户配置区 开始 #####
    10. #
    11. #
    12. # 项目根目录,推荐将此脚本放在项目的根目录,这里就不用改了
    13. # 应用名,确保和Xcode里Product下的target_name.app名字一致
    14. #
    15. ##### 用户配置区 结束  #####

    如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,怎么办呢?每一行加个#符号太费力了,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。

     

    Shell字符串

     

    字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似。

    单引号

    1. str='this is a string'

    单引号字符串的限制:

    • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
    • 单引号字串中不能出现单引号(对单引号使用转义符后也不行)。

    双引号

    1. your_name='qinjx'
    2. str="Hello, I know your are \"$your_name\"! \n"

    双引号的优点:

    • 双引号里可以有变量
    • 双引号里可以出现转义字符

    拼接字符串

    1. your_name="qinjx"
    2. greeting="hello, "$your_name" !"
    3. greeting_1="hello, ${your_name} !"
    4.  
    5. echo $greeting $greeting_1

    获取字符串长度

    1. string="abcd"
    2. echo ${#string} #输出 4

    提取子字符串

    1. string="alibaba is a great company"
    2. echo ${string:1:4} #输出liba

    查找子字符串

    1. string="alibaba is a great company"
    2. echo `expr index "$string" is`

     

    Shell数组:shell数组的定义、数组长度

    Shell在编程方面比Windows批处理强大很多,无论是在循环、运算。

    bash支持一维数组(不支持多维数组),并且没有限定数组的大小。类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。

    定义数组

    在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为:
        array_name=(value1 ... valuen)
    例如:

    1. array_name=(value0 value1 value2 value3)

    或者

    1. array_name=(
    2. value0
    3. value1
    4. value2
    5. value3
    6. )


    还可以单独定义数组的各个分量:

    1. array_name[0]=value0
    2. array_name[1]=value1
    3. array_name[2]=value2

    可以不使用连续的下标,而且下标的范围没有限制。

    读取数组

    读取数组元素值的一般格式是:
        ${array_name[index]}
    例如:

    1. valuen=${array_name[2]}

    举个例子:

    1. #!/bin/sh
    2.  
    3. NAME[0]="Zara"
    4. NAME[1]="Qadir"
    5. NAME[2]="Mahnaz"
    6. NAME[3]="Ayan"
    7. NAME[4]="Daisy"
    8. echo "First Index: ${NAME[0]}"
    9. echo "Second Index: ${NAME[1]}"

    运行脚本,输出:

    $./test.sh

    First Index: Zara

    Second Index: Qadir

    使用@ 或 * 可以获取数组中的所有元素,例如:

    1. ${array_name[*]}
    2. ${array_name[@]}

    举个例子:

    1. #!/bin/sh
    2.  
    3. NAME[0]="Zara"
    4. NAME[1]="Qadir"
    5. NAME[2]="Mahnaz"
    6. NAME[3]="Ayan"
    7. NAME[4]="Daisy"
    8. echo "First Method: ${NAME[*]}"
    9. echo "Second Method: ${NAME[@]}"

    运行脚本,输出:

    $./test.sh

    First Method: Zara Qadir Mahnaz Ayan Daisy

    Second Method: Zara Qadir Mahnaz Ayan Daisy

    获取数组的长度

    获取数组长度的方法与获取字符串长度的方法相同,例如:

    1. # 取得数组元素的个数
    2. length=${#array_name[@]}
    3. # 或者
    4. length=${#array_name[*]}
    5. # 取得数组单个元素的长度
    6. lengthn=${#array_name[n]}

     

    Shell echo命令

    echo是Shell的一个内部指令,用于在屏幕上打印出指定的字符串。命令格式:

    1. echo arg

    您可以使用echo实现更复杂的输出格式控制。

    显示转义字符

    1. echo "\"It is a test\""

    结果将是:
    "It is a test"

    双引号也可以省略。

    显示变量

    1. name="OK"
    2. echo "$name It is a test"

    结果将是:
    OK It is a test

    同样双引号也可以省略。

    如果变量与其它字符相连的话,需要使用大括号({ }):

    1. mouth=8
    2. echo "${mouth}-1-2009"

    结果将是:
    8-1-2009

    显示换行

    1. echo "OK!\n"
    2. echo "It is a test"

    输出:
    OK!
    It is a test

    显示不换行

    1. echo "OK!\c"
    2. echo "It is a test"

    输出:
    OK!It si a test

    显示结果重定向至文件

    1. echo "It is a test" > myfile

    原样输出字符串

    若需要原样输出字符串(不进行转义),请使用单引号。例如:

    1. echo '$name\"'

    显示命令执行结果

    1. echo `date`

    结果将显示当前日期

    从上面可看出,双引号可有可无,单引号主要用在原样输出中。

     

    shell printf命令:格式化输出语句

     

    printf 命令用于格式化输出, 是echo命令的增强版。它是C语言printf()库函数的一个有限的变形,并且在语法上有些不同。

    注意:printf 由 POSIX 标准所定义,移植性要比 echo 好。

    如同 echo 命令,printf 命令也可以输出简单的字符串:

    1. $printf "Hello, Shell\n"
    2. Hello, Shell
    3. $

    printf 不像 echo 那样会自动换行,必须显式添加换行符(\n)。

    printf 命令的语法:

    printf format-string  [arguments...]

    format-string 为格式控制字符串,arguments为参数列表。

    printf()在C语言入门教程中已经讲到,功能和用法与 printf 命令类似,请查看:C语言格式输出函数printf()详解

    这里仅说明与C语言printf()函数的不同:

    • printf 命令不用加括号
    • format-string 可以没有引号,但最好加上,单引号双引号均可。
    • 参数多于格式控制符(%)时,format-string 可以重用,可以将所有参数都转换。
    • arguments 使用空格分隔,不用逗号。


    请看下面的例子:

    1. # format-string为双引号
    2. printf "%d %s\n" 1 "abc"
    3. 1 abc
    4. # 单引号与双引号效果一样
    5. printf '%d %s\n' 1 "abc"
    6. 1 abc
    7. # 没有引号也可以输出
    8. printf %s abcdef
    9. abcdef
    10. # 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
    11. printf %s abc def
    12. abcdef
    13. printf "%s\n" abc def
    14. abc
    15. def
    16. printf "%s %s %s\n" a b c d e f g h i j
    17. a b c
    18. d e f
    19. g h i
    20. j
    21. # 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
    22. printf "%s and %d \n"
    23. and 0
    24. # 如果以 %d 的格式来显示字符串,那么会有警告,提示无效的数字,此时默认置为 0
    25. printf "The first program always prints'%s,%d\n'" Hello Shell
    26. -bash: printf: Shell: invalid number
    27. The first program always prints 'Hello,0'
    28. $


    注意,根据POSIX标准,浮点格式%e、%E、%f、%g与%G是“不需要被支持”。这是因为awk支持浮点预算,且有它自己的printf语句。这样Shell程序中需要将浮点数值进行格式化的打印时,可使用小型的awk程序实现。然而,内建于bash、ksh93和zsh中的printf命令都支持浮点格式。

     

    Shell if else语句

     

    if 语句通过关系运算符判断表达式的真假来决定执行哪个分支。Shell 有三种 if ... else 语句:

    • if ... fi 语句;
    • if ... else ... fi 语句;
    • if ... elif ... else ... fi 语句。

    1) if ... else 语句

    if ... else 语句的语法:

    if [ expression ]

    then

      Statement(s) to be executed if expression is true

    fi

    如果 expression 返回 true,then 后边的语句将会被执行;如果返回 false,不会执行任何语句。

    最后必须以 fi 来结尾闭合 if,fi 就是 if 倒过来拼写,后面也会遇见。

    注意:expression 和方括号([ ])之间必须有空格,否则会有语法错误。

    举个例子:

    1. #!/bin/sh
    2.  
    3. a=10
    4. b=20
    5.  
    6. if [ $a == $b ]
    7. then
    8.    echo "a is equal to b"
    9. fi
    10.  
    11. if [ $a != $b ]
    12. then
    13.    echo "a is not equal to b"
    14. fi

    运行结果:

    a is not equal to b

    2) if ... else ... fi 语句

    if ... else ... fi 语句的语法:

    if [ expression ]

    then

      Statement(s) to be executed if expression is true

    else

      Statement(s) to be executed if expression is not true

    fi

    如果 expression 返回 true,那么 then 后边的语句将会被执行;否则,执行 else 后边的语句。

    举个例子:

    1. #!/bin/sh
    2.  
    3. a=10
    4. b=20
    5.  
    6. if [ $a == $b ]
    7. then
    8.    echo "a is equal to b"
    9. else
    10.    echo "a is not equal to b"
    11. fi

    执行结果:

    a is not equal to b

    3) if ... elif ... fi 语句

    if ... elif ... fi 语句可以对多个条件进行判断,语法为:

    if [ expression 1 ]

    then

      Statement(s) to be executed if expression 1 is true

    elif [ expression 2 ]

    then

      Statement(s) to be executed if expression 2 is true

    elif [ expression 3 ]

    then

      Statement(s) to be executed if expression 3 is true

    else

      Statement(s) to be executed if no expression is true

    fi

    哪一个 expression 的值为 true,就执行哪个 expression 后面的语句;如果都为 false,那么不执行任何语句。

    举个例子:

    1. #!/bin/sh
    2.  
    3. a=10
    4. b=20
    5.  
    6. if [ $a == $b ]
    7. then
    8.    echo "a is equal to b"
    9. elif [ $a -gt $b ]
    10. then
    11.    echo "a is greater than b"
    12. elif [ $a -lt $b ]
    13. then
    14.    echo "a is less than b"
    15. else
    16.    echo "None of the condition met"
    17. fi

    运行结果:

    a is less than b


    if ... else 语句也可以写成一行,以命令的方式来运行,像这样:

    1. if test $[2*3] -eq $[1+5]; then echo 'The two numbers are equal!'; fi;


    if ... else 语句也经常与 test 命令结合使用,如下所示:

    1. num1=$[2*3]
    2. num2=$[1+5]
    3. if test $[num1] -eq $[num2]
    4. then
    5.     echo 'The two numbers are equal!'
    6. else
    7.     echo 'The two numbers are not equal!'
    8. fi

    输出:

    The two numbers are equal!

    test 命令用于检查某个条件是否成立,与方括号([])类似。

     

    Shell case esac语句

     

    case ... esac 与其他语言中的switch ... case 语句类似,是一种多分枝选择结构。

    case 语句匹配一个值或一个模式,如果匹配成功,执行相匹配的命令。case语句格式如下:

    case 值 in

    模式1)

       command1

       command2

       command3

       ;;

    模式2)

       command1

       command2

       command3

       ;;

    *)

       command1

       command2

       command3

       ;;

    esac

    case工作方式如上所示。取值后面必须为关键字in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。

    取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。

    下面的脚本提示输入1到4,与每一种模式进行匹配:

    1. echo 'Input a number between 1 to 4'
    2. echo 'Your number is:\c'
    3. read aNum
    4. case $aNum in
    5.     1)  echo 'You select 1'
    6.     ;;
    7.     2)  echo 'You select 2'
    8.     ;;
    9.     3)  echo 'You select 3'
    10.     ;;
    11.     4)  echo 'You select 4'
    12.     ;;
    13.     *)  echo 'You do not select a number between 1 to 4'
    14.     ;;
    15. esac

    输入不同的内容,会有不同的结果,例如:

    Input a number between 1 to 4

    Your number is:3

    You select 3


    再举一个例子:

    1. #!/bin/bash
    2.  
    3. option="${1}"
    4. case ${option} in
    5.    -f) FILE="${2}"
    6.       echo "File name is $FILE"
    7.       ;;
    8.    -d) DIR="${2}"
    9.       echo "Dir name is $DIR"
    10.       ;;
    11.    *)
    12.       echo "`basename ${0}`:usage: [-f file] | [-d directory]"
    13.       exit 1 # Command to come out of the program with status 1
    14.       ;;
    15. esac

    运行结果:

    $./test.sh

    test.sh: usage: [ -f filename ] | [ -ddirectory ]

    $ ./test.sh -f index.htm

    $ vi test.sh

    $ ./test.sh -f index.htm

    File name is index.htm

    $ ./test.sh -d unix

    Dir name is unix

    $

     

    Shell for循环

     

     

    与其他编程语言类似,Shell支持for循环。

    for循环一般格式为:

    for 变量 in 列表

    do

       command1

       command2

       ...

       commandN

    done

    列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。

    in 列表是可选的,如果不用它,for 循环使用命令行的位置参数。

    例如,顺序输出当前列表中的数字:

    1. for loop in 1 2 3 4 5
    2. do
    3.     echo "The value is: $loop"
    4. done

    运行结果:

    The value is: 1

    The value is: 2

    The value is: 3

    The value is: 4

    The value is: 5


    顺序输出字符串中的字符:

    1. for str in 'This is a string'
    2. do
    3.     echo $str
    4. done

    运行结果:

    This is a string


    显示主目录下以 .bash 开头的文件:

    1. #!/bin/bash
    2.  
    3. for FILE in $HOME/.bash*
    4. do
    5.    echo $FILE
    6. done

    运行结果:

    /root/.bash_history

    /root/.bash_logout

    /root/.bash_profile

    /root/.bashrc

     

    Shell while循环

     

    while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。其格式为:

    while command

    do

       Statement(s) to be executed ifcommand is true

    done

    命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。

    以下是一个基本的while循环,测试条件是:如果COUNTER小于5,那么返回 true。COUNTER从0开始,每次循环处理时,COUNTER加1。运行上述脚本,返回数字1到5,然后终止。

    1. COUNTER=0
    2. while [ $COUNTER -lt 5 ]
    3. do
    4.     COUNTER='expr $COUNTER+1'
    5.     echo $COUNTER
    6. done

    运行脚本,输出:

    1

    2

    3

    4

    5


    while循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量FILM,按<Ctrl-D>结束循环。

    1. echo 'type <CTRL-D> to terminate'
    2. echo -n 'enter your most liked film: '
    3. while read FILM
    4. do
    5.     echo "Yeah! great film the $FILM"
    6. done

    运行脚本,输出类似下面:

    type <CTRL-D> to terminate

    enter your most liked film: Sound of Music

    Yeah! great film the Sound of Music

    Shell until循环

    until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。

    until 循环格式为:

    until command

    do

      Statement(s) to be executed until command is true

    done

    command 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。

    例如,使用 until 命令输出 0 ~ 9 的数字:

    1. #!/bin/bash
    2.  
    3. a=0
    4.  
    5. until [ ! $a -lt 10 ]
    6. do
    7.    echo $a
    8.    a=`expr $a + 1`
    9. done

    运行结果:

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    Shell breakcontinue命令

     

    在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,像大多数编程语言一样,Shell也使用 break 和 continue 来跳出循环。

    break命令

    break命令允许跳出所有循环(终止执行后面的所有循环)。

    下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,就要使用break命令。

    1. #!/bin/bash
    2. while :
    3. do
    4.     echo -n "Input a number between 1 to 5: "
    5.     read aNum
    6.     case $aNum in
    7.         1|2|3|4|5) echo "Your number is $aNum!"
    8.         ;;
    9.         *) echo "You do not select a number between 1 to 5, game is over!"
    10.             break
    11.         ;;
    12.     esac
    13. done

    在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环。例如:

    1. break n

    表示跳出第 n 层循环。

    下面是一个嵌套循环的例子,如果 var1 等于 2,并且 var2 等于 0,就跳出循环:

    1. #!/bin/bash
    2.  
    3. for var1 in 1 2 3
    4. do
    5.    for var2 in 0 5
    6.    do
    7.       if [ $var1 -eq 2 -a $var2 -eq 0 ]
    8.       then
    9.          break 2
    10.       else
    11.          echo "$var1 $var2"
    12.       fi
    13.    done
    14. done

    如上,break 2 表示直接跳出外层循环。运行结果:

    1 0

    1 5

    continue命令

    continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

    对上面的例子进行修改:

    1. #!/bin/bash
    2. while :
    3. do
    4.     echo -n "Input a number between 1 to 5: "
    5.     read aNum
    6.     case $aNum in
    7.         1|2|3|4|5) echo "Your number is $aNum!"
    8.         ;;
    9.         *) echo "You do not select a number between 1 to 5!"
    10.             continue
    11.             echo "Game is over!"
    12.         ;;
    13.     esac
    14. done

    运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句

    1. echo "Game is over!"

    永远不会被执行。

    同样,continue 后面也可以跟一个数字,表示跳出第几层循环。

    再看一个 continue 的例子:

    1. #!/bin/bash
    2.  
    3. NUMS="1 2 3 4 5 6 7"
    4.  
    5. for NUM in $NUMS
    6. do
    7.    Q=`expr $NUM % 2`
    8.    if [ $Q -eq 0 ]
    9.    then
    10.       echo "Number is an even number!!"
    11.       continue
    12.    fi
    13.    echo "Found odd number"
    14. done

    运行结果:

    Found odd number

    Number is an even number!!

    Found odd number

    Number is an even number!!

    Found odd number

    Number is an even number!!

    Found odd number

     

    Shell函数:Shell函数返回值、删除函数、在终端调用函数

     

    函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高。像其他编程语言一样,Shell 也支持函数。Shell 函数必须先定义后使用。

    Shell 函数的定义格式如下:

    function_name () {

       list of commands

        [return value ]

    }

    如果你愿意,也可以在函数名前加上关键字 function:

    function function_name () {

       list of commands

        [return value ]

    }

    函数返回值,可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值。

    Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。如果 return 其他数据,比如一个字符串,往往会得到错误提示:“numeric argument required”。

    如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。

    先来看一个例子:

    1. #!/bin/bash
    2.  
    3. # Define your function here
    4. Hello () {
    5.    echo "Url is http://see.xidian.edu.cn/cpp/shell/"
    6. }
    7.  
    8. # Invoke your function
    9. Hello

    运行结果:

    $./test.sh

    Hello World

    $

    调用函数只需要给出函数名,不需要加括号。

    再来看一个带有return语句的函数:

    1. #!/bin/bash
    2. funWithReturn(){
    3.     echo "The function is to get the sum of two numbers..."
    4.     echo -n "Input first number: "
    5.     read aNum
    6.     echo -n "Input another number: "
    7.     read anotherNum
    8.     echo "The two numbers are $aNum and $anotherNum !"
    9.     return $(($aNum+$anotherNum))
    10. }
    11. funWithReturn
    12. # Capture value returnd by last command
    13. ret=$?
    14. echo "The sum of two numbers is $ret !"

    运行结果:

    The function is to get the sum of twonumbers...

    Input first number: 25

    Input another number: 50

    The two numbers are 25 and 50 !

    The sum of two numbers is 75 !

    函数返回值在调用该函数后通过 $? 来获得。

    再来看一个函数嵌套的例子:

    1. #!/bin/bash
    2.  
    3. # Calling one function from another
    4. number_one () {
    5.    echo "Url_1 is http://see.xidian.edu.cn/cpp/shell/"
    6.    number_two
    7. }
    8.  
    9. number_two () {
    10.    echo "Url_2 is http://see.xidian.edu.cn/cpp/u/xitong/"
    11. }
    12.  
    13. number_one

    运行结果:

    Url_1 is http://see.xidian.edu.cn/cpp/shell/

    Url_2 ishttp://see.xidian.edu.cn/cpp/u/xitong/

    像删除变量一样,删除函数也可以使用 unset 命令,不过要加上 .f 选项,如下所示:

    1. $unset .f function_name

    如果你希望直接从终端调用函数,可以将函数定义在主目录下的 .profile 文件,这样每次登录后,在命令提示符后面输入函数名字就可以立即调用。

     

    Shell函数参数

     

    在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...

    带参数的函数示例:

    1. #!/bin/bash
    2. funWithParam(){
    3.     echo "The value of the first parameter is $1 !"
    4.     echo "The value of the second parameter is $2 !"
    5.     echo "The value of the tenth parameter is $10 !"
    6.     echo "The value of the tenth parameter is ${10} !"
    7.     echo "The value of the eleventh parameter is ${11} !"
    8.     echo "The amount of the parameters is $# !"  # 参数个数
    9.     echo "The string of the parameters is $* !"  # 传递给函数的所有参数
    10. }
    11. funWithParam 1 2 3 4 5 6 7 8 9 34 73

    运行脚本:

    The value of the first parameter is 1 !

    The value of the second parameter is 2 !

    The value of the tenth parameter is 10 !

    The value of the tenth parameter is 34 !

    The value of the eleventh parameter is 73 !

    The amount of the parameters is 12 !

    The string of the parameters is 1 2 3 4 5 67 8 9 34 73 !"

    注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

    另外,还有几个特殊变量用来处理参数,前面已经提到:

    特殊变量

    说明

    $#

    传递给函数的参数个数。

    $*

    显示所有传递给函数的参数。

    $@

    与$*相同,但是略有区别,请查看Shell特殊变量

    $?

    函数的返回值。

     

    Shell输入输出重定向:ShellHere Document/dev/null文件

     

    Unix 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器。

    输出重定向

    命令的输出不仅可以是显示器,还可以很容易的转移向到文件,这被称为输出重定向。

    命令输出重定向的语法为:

    1. command > file

    这样,输出到显示器的内容就可以被重定向到文件。

    例如,下面的命令在显示器上不会看到任何输出:

    1. $ who > users

    打开 users 文件,可以看到下面的内容:

    $ cat users

    oko        tty01   Sep 12 07:30

    ai         tty15   Sep 12 13:32

    ruth       tty21   Sep 12 10:10

    pat        tty24   Sep 12 13:07

    steve      tty25   Sep 12 13:03

    $

    输出重定向会覆盖文件内容,请看下面的例子:

    $ echo line 1 > users

    $ cat users

    line 1

    $

    如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:

    $ echo line 2 >> users

    $ cat users

    line 1

    line 2

    $

    输入重定向

    和输出重定向一样,Unix 命令也可以从文件获取输入,语法为:

    1. command < file

    这样,本来需要从键盘获取输入的命令会转移到文件读取内容。

    注意:输出重定向是大于号(>),输入重定向是小于号(<)。

    例如,计算 users 文件中的行数,可以使用下面的命令:

    $ wc -l users

    2 users

    $

    也可以将输入重定向到 users 文件:

    $ wc -l < users

    2

    $

    注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。

    重定向深入讲解

    一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

    • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
    • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
    • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。


    默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。

    如果希望 stderr 重定向到 file,可以这样写:

    1. $command 2 > file

    如果希望 stderr 追加到 file 文件末尾,可以这样写:

    1. $command 2 >> file

    2 表示标准错误文件(stderr)。

    如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:

    1. $command > file 2>&1

    1. $command >> file 2>&1

    如果希望对 stdin 和 stdout 都重定向,可以这样写:

    1. $command < file1 >file2

    command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。 

    全部可用的重定向命令列表

    命令

    说明

    command > file

    将输出重定向到 file。

    command < file

    将输入重定向到 file。

    command >> file

    将输出以追加的方式重定向到 file。

    n > file

    将文件描述符为 n 的文件重定向到 file。

    n >> file

    将文件描述符为 n 的文件以追加的方式重定向到 file。

    n >& m

    将输出文件 m 和 n 合并。

    n <& m

    将输入文件 m 和 n 合并。

    << tag

    将开始标记 tag 和结束标记 tag 之间的内容作为输入。

    Here Document

    Here Document 目前没有统一的翻译,这里暂译为”嵌入文档“。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本的形式如下:

    1. command << delimiter
    2.     document
    3. delimiter

    它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。

    注意:

    • 结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
    • 开始的delimiter前后的空格会被忽略掉。


    下面的例子,通过 wc -l 命令计算 document 的行数:

    $wc -l << EOF

       This is a simple lookup program

       for good (and bad) restaurants

       in Cape Town.

    EOF

    3

    $

    也可以将 Here Document 用在脚本中,例如:

    1. #!/bin/bash
    2.  
    3. cat << EOF
    4. This is a simple lookup program
    5. for good (and bad) restaurants
    6. in Cape Town.
    7. EOF

    运行结果:

    This is a simple lookup program

    for good (and bad) restaurants

    in Cape Town.


    下面的脚本通过 vi 编辑器将 document 保存到 test.txt 文件:

    1. #!/bin/sh
    2.  
    3. filename=test.txt
    4. vi $filename <<EndOfCommands
    5. i
    6. This file was created automatically from
    7. a shell script
    8. ^[
    9. ZZ
    10. EndOfCommands

    运行脚本:

    $ sh test.sh

    Vim: Warning: Input is not from a terminal

    $

    打开 test.txt,可以看到下面的内容:

    $ cat test.txt

    This file was created automatically from

    a shell script

    $

    /dev/null 文件

    如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:

    1. command > /dev/null

    /dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。

    如果希望屏蔽 stdout 和 stderr,可以这样写:

    1. command > /dev/null 2>&1

     

    Shell文件包含

     

    像其他语言一样,Shell 也可以包含外部脚本,将外部脚本的内容合并到当前脚本。

    Shell 中包含脚本可以使用:

    1. . filename

    1. source filename

    两种方式的效果相同,简单起见,一般使用点号(.),但是注意点号(.)和文件名中间有一空格。

    例如,创建两个脚本,一个是被调用脚本 subscript.sh,内容如下:

    1. url="http://see.xidian.edu.cn/cpp/view/2738.html"

    一个是主文件 main.sh,内容如下:

    1. #!/bin/bash
    2. . ./subscript.sh
    3. echo $url

    执行脚本:

    $chomd +x main.sh

    ./main.sh

    http://see.xidian.edu.cn/cpp/view/2738.html

    $

    注意:被包含脚本不需要有执行权限。

    展开全文
  • Shell 的作用是解释执行用户的命令,用户输入一条命令,shell 就行一条,这种方式成为交互式,还有另外一种方式,就是用户事先写一个 shell 脚本,包含很多命令,然后让 shell 一次性的进行执行,这种方式被...



    〇、简介

    Shell 的作用是解释执行用户的命令,用户输入一条命令,shell 就行一条,这种方式成为交互式,还有另外一种方式,就是用户事先写一个 shell 脚本,包含很多命令,然后让 shell 一次性的进行执行,这种方式被称为“批处理方式”

    一般我们在UNIX中使用的 shell 就是 bash 和 sh,当然也有其他 shell,在 UNIX 环境下可以使用 /etc/shells: valid login shells 命令来显示所有的 shell,想要切换,直接输入 shell 名即可


    一、Shell 的启动

    bash 启动脚本是 bash 启动时会自动执行的脚本,因此用户可以把一些环境变量的设置和 alias、umask 设置等放到启动脚本中,这样每次启动 shell 时都会自动生效

    但是,启动 bash 的方法不同,执行启动脚本的步骤也不同


    1、作为交互登录 Shell 启动,或者使用 --login 参数启动

    交互 Shell 指的是用户在提示符下输入命令的 Shell,而不是执行脚本的 shell

    这样启动 bash 会自动执行以下脚本:

    1. 执行 /etc/profil,系统中的每个用户登录时都执行,只有管理员可以修改

    2. 然后依次执行当前用户主目录的 ~/.bash_profile、~/.bash_login 和 ~/.profile 三个文件(如果存在的话)

    3. 在Shell 退出时,会执行 ~/.bash_logout 脚本(如果存在的话)


    通常在 ~/.bash_profile 中会有下面几行:

    这样,如果 ~/.bashrc 文件存在,还会调用这个脚本


    2、以交互非登录 Shell 启动

    比如在图形界面下开一个终端窗口,或者在登录 Shell 提示符下再输入 bash 命令,就得到一个交互非登录的 shell

    这种 shell 在启动时自动执行 ~/.bashrc 脚本

    如上面所讲,如果要在启动脚本中做某些设置,使它在图形终端窗口和字符终端的Shell中都起作用,最好就是在 ~/.bashrc 中设置。


    如果终端或远程登录,那么登录 Shell 是该用户的所有其他进程的父进程,所以环境变量在登录 Shell 的启动脚本里设置一次就可以自动带到其他非登录 Shell 里,而本地变量、函数、 alias 等设置没有办法带到子Shell里,需要每次启动非登录Shell时设置一遍,所以就需要有非登录Shell的启动脚本,所以一般来说在 ~/.bash_profile 里设置环境变量,在 ~/.bashrc 里设置本地变量、函数、 alias 等。

    如果你的Linux带有图形系统则不能这样设置,由于从图形界面的窗口管理器登录并不会产生登录Shell,所以环境变量也应该在 ~/.bashrc 里设置。


    3、非交互式启动

    为了执行脚本而 fork 出来的子 Shell 是非交互式 Shell,启动时执行的脚本文件有环境变量 BASH_ENV 定义,相当于执行下面的命令:

              if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi


    4、以 sh 命令启动

    如果以 sh 命令启动 bash,bash 将模拟 sh 的行为

    如果作为交互登录 shell 启动,则会一次执行:

    1. /etc/profile

    2. ~/.profile


    如果作为交互式 Shell 启动,相当于执行

              if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi

    以 #! /bin/sh 开头的脚本就是这种情况,不会运行任何脚本


    二、Shell 如何执行命令

    1、执行交互式命令

    凡是使用 which 命令查不到程序文件所在位置的命令都是 shell 的内建命令,这些命令相当于 Shell 的进程中的一个函数,没有单独的 man 手册,可以使用下面的命令查看: man bash-builtins

    对于非内建命令,Shell 会 fork 并 exec 该命令,创建一个新的进程,但是对于内建命令则不会,如 cd、alias、umask、exit、export、shift、if、eval、[、for、while 等命令都是内建命令

    虽然内建命令不创建新的进程,但是也会有返回值,通常也用 0 表示调用成功,这个返回值被称为 Exit Status (状态码),可以使用特殊变量 $? 读出


    2、执行脚本

    shell 脚本中用 # 表示注释,相当于 C 语言中的 // 注释,但是#! 却表示该脚本使用 /bin/sh 解释执行,并不代表注释

    如下例是一个简单的 shell 脚本:

    将上面的代码保存在 .sh 文件中,即为一个 Shell 脚本

    执行脚本只需要输入命令:./script.sh,这是 sh ./script.sh 命令的简写


    执行上面的脚本的步骤为:


    三、Shell 的基本语法

    1、变量

    一般的,Shell 变量由全大写字母加下划线组成,有两种类型的 Shell 变量

    (1)环境变量

    环境变量可以从父进程传给子进程,因此 Shell 进程的环境变量可以从当前的 Shell 进程传给 fork 出来的子进程,使用 printenv 命令可以显示当前 Shell 进程的环境变量


    (2)本地变量

    只存在于当前 Shell 进程的变量,用set命令可以显示当前 Shell 进程中定义的所有变量(包括本地变量和环境变量)和函数


    • set

    显示当前所有变量和函数


    • 变量的定义

    环境变量是任何进程都有的概念,而本地变量是 Shell 所特有的概念,在 Shell 中,环境变量和本地变量的定义和用法相似,在 Shell 中定义或赋值一个变量可以使用下面的格式:

               VARNAME=value

    等号两边是不能有空格的,否则就会被解释成命令或命令行参数


    • export -- 将变量导出为环境变量

    任何一个变量定义后都仅存在于当前的 Shell 进程,是本地变量,用 export 命令可以把本地变量导出为环境变量,定义和导出环境变量也可以一步完成

               export VARNAME=value

    当然也可以分两步:

               VARNAME=value

               export VARNAME


    • unset -- 删除变量

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

               unset VARNAME


    • echo -- 显示变量的值

    使用 echo 命令可以显示变量的值


    一般对于 VARNAME 变量,我们使用 ${VARNAME} 表示他的值,在不引起歧义的情况下,我们也可以直接使用 VARNAME 表示他的值


    Shell 中的所有变量都是字符串,Shell中的变量也不需要先定义后使用,使用一个没有定义的变量,这个变量的值为空字符串


    2、通配符 -- *、?、[]

    Shell 中也有通配符,如下表:

    如我们可以使用 ls  ch0[012].doc 命令查找文件,如果当前目录下有 ch00.doc 和 ch02.doc,ls 的参数会直接转换成这两个文件名,而不是一个匹配字符串


    3、命令代换 -- `或 $()

    由反引号(键盘上 ESC 键下面的,主键盘区左上角·/~)所引起来的也是一条命令,Shell 会首先执行反引号中的命令,然后将结果代换到原来的位置进行原命令的执行,如下面的命令:

               DATE=`date`

               echo $DATE

    反引号和 $() 是一样的:

               DATE=$(date)

               echo $DATE


    4、算术代换 -- $(())

    Shell 会将 $(()) 中的 Shell 变量的取值转换成整数用于算术计算(其他情况下 Shell 都将变量视为字符串,无法进行算术计算)

               VAR=45

               echo $(($VAR+3))

    会显示 48

    $(()) 中只能进行 +、-、*、/ 和 () 运算,并且只能进行整数运算


    5、转义字符 -- \

    和 C 语言一样,Shell 中也需要转义字符,如 \ 、\$、\\、\`、\"


    6、字符串 -- '、"

    在 Shell 中单引号中的所有字符都被认为是普通的字符,所以不需要转义字符,如运行:

               $ echo '$SHELL'

    会显示 $SHELL

               $ echo 'ABC\\'

    会显示 ABC\\


    双引号也将其中的字符串视为字面值,但是反引号、$、转义字符等等都保持原来的意义

    如:

               $ echo "$SHELL"

    会显示 /bin/bash


               $echo "`date`"

    会显示 Sun Apr 20 11:22:06 CEST 2003


    五、Shell 脚本语法

    1、条件测试 -- test、[]

    命令 test 或 [] 可以测试一个条件是否城里,如果测试结果为真,则该命令的 Exit Status 为0,如果测试结果为假,则命令的 Exit Status 为1(与C语言中正好相反)

    由于 [] 中的 [ 实际上是一个命令,他后面的都是这个命令的参数,因此需要用空格隔开

    如下例:


    常见测试命令如下所示:


    与 C 语言类似,测试条件之间还可以做与、或、非逻辑运算


    如:


    需要注意的是,如果上例中的 VAR 变量没有被预先定义,那么就会被解释器展开为空字符串,整个命令就变成了:

               [ -d Desktop -a = 'abc' ] 

    这就成了一个语法错误

    为了避免这样的意外情况发生,一个好的 Shell 编程习惯总是把变量取值放到双引号之中:


    这样,虽然 VAR 没有被预先定义,但是命令还是被展开成了

                [ -d Desktop -a "" = 'abc' ]


    2、分支控制 -- if、then、elif、else、fi

    和 C 语言类似,在 Shell 中使用 if、then、elif、else、fi 几个命令实现分支控制,例如:

               if [ -f ~/.bashrc ]; then

                          .~/.bashrc

               fi


    • :

    : 是一个特殊的指令,称为“空命令”,该命令不做任何事,但是 Exit Status 总是真,也可以使用 /bin/true 或 /bin/false 获得总是真或假的 Exit Status

               if :; then echo "always true"; fi

    与下面的例子是一样的:

               if /bin/true; then echo "always true"; fi


    • read

    我们也可以使用 read 命令等待用户键入一行字符串,存到一个 Shell 变量中


    • &&、||

    与C语言类似,Shell 也提供 && 与 ||

              test "$VAR" -gt 1 -a "$VAR" -lt 3

    等价于

              test "$VAR" -gt 1 && test "$VAR" -lt 3


    因为 && 操作的短路求值特性,很多 Shell 脚本喜欢写成:

             test "$(whoami)" != 'root' && (echo you are using a nonprivileged account; exit 1)


    3、case、esac

    case 命令类似于 C 语言的 switch/case 语句,esac 用来标志 case 语句块的结束

    Shell 中的 case 语句不仅可以用来匹配数字,也可以用来匹配字符串和通配符


    如下例,每个匹配分支都可以有若干条命令,末尾必须以;;结束


    下面的例子常常用在某服务的处理上,比如对于apache,我们可以输入 apache start 这个命令启动他,也可以使用 apache stop 这个命令终止他


    $0、$1 ... 等等指的是取命令的第0个参数、第1个参数。。。


    4、for、do、done

    Shell 脚本的 for 循环结构和 C 语言很不一样,他类似于某些编程语言的 foreach 循环

    如下面的例子:


    例子中,FRUIT 是一个变量,让这个变量依次取值为 apple、banana、pear 做循环,done 用来标志循环结束


    如果目录下有 chap0、chap1、chap2 等文件,下面的循环将他们重命名为 chap0~ 、 chap1~ 、 chap2~ 等

              $ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done


    5、while、do、done

    while 的用法和 C 语言非常类似,比如下面是一个验证密码的脚本:


    我们也可以像 C 语言中那样控制 while 循环的循环次数


    6、一些特殊的变量

    有很多变量是被 Shell 自动赋值的,如下表


    参数 $n 被称为“位置参数”

    • shift

    shift 命令可以令位置参数左移,比如 shift 3 表示让 $4 变成 $1,$5 变成 $2,原来的 $1、$2就会被丢弃掉,而 $0 不移动

    不带参数的 shift 命令相当于 shift 1


    7、函数

    Shell 中的函数定义中没有返回值也没有参数列表

    如下面例子所示:


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

    Shell脚本中的函数必须先定义后调用,一般把函数定义都写在脚本的前面,把函数调用和其它命令写在脚本的最后(类似C语言中的 main 函数,这才是整个脚本实际开始执行命令的地方)。


    Shell函数没有参数列表并不表示不能传参数,事实上,函数就像是迷你脚本,调用函数时可以传任意个参数,在函数内同样是用 $0 、 $1 、 $2 等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的 $0 、 $1 、 $2 等变量。函数中可以用 return 命令返回,如果 return 后面跟一个数字则表示函数的Exit Status。


    六、Shell 脚本的调试方法

    Shell提供了一些用于调试脚本的选项:

    • -n

    读一遍脚本中的命令但是不执行,用于检查脚本中的语法错误

    • -v

    一边执行脚本,一边将执行过的脚本命令打印到标准错误输出

    • -x

    提供跟踪执行信息,将执行的每一条命令和结果依次打印出来


    有三种方法使用这些选项:

    1. 在命令行提供参数   $ sh -x ./script.sh

    2. 在脚本开头提供参数    #! /bin/sh -x

    3. 在脚本中用 set 命令启用或禁用参数



    下面是我写的一个脚本,在打开终端的时候给出人性化的提示,并且询问是否将虚拟机中的重要文件保存在宿主机上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #
    # file: .init.sh
    # author: 龙泉居士
    # date: 2013-05-06 18:51
    #
       
    echo "\033[44m`date`\033[0m"
    echo "\033[44mHello zeyu!\033[0m"
    echo ""
       
    SAVE_OR_NOT="no"
    read -p "Do you want to save the Workspace [yes/no](no): " SAVE_OR_NOT
    if "$SAVE_OR_NOT" "yes" ];
    then sh ~/Workspace/.save.sh 
    fi
    exit 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #
    # file: .save.sh
    # author: 龙泉居士
    # date: 2013-05-06 18:52
    #
       
    cd ~/Workspace
    mysqldump -u root -p150102 zeyu_student > sql/zeyu_student.sql
    mysqldump -u root -p150102 zeyu_custom > sql/zeyu_custom.sql
    tar -cvf Workspace.tar *
    mv Workspace.tar /mnt/hgfs/Debin/

    然后在~/.bashrc 中添加下面的代码

    1
    2
    3
    4
    # added by zeyu 2013/05/06
    cd Workspace
    sh ~/Workspace/.init.sh
    export PATH=$PATH:/home/zeyu/Workspace
    〇、简介

    Shell 的作用是解释执行用户的命令,用户输入一条命令,shell 就行一条,这种方式成为交互式,还有另外一种方式,就是用户事先写一个 shell 脚本,包含很多命令,然后让 shell 一次性的进行执行,这种方式被称为“批处理方式”

    一般我们在UNIX中使用的 shell 就是 bash 和 sh,当然也有其他 shell,在 UNIX 环境下可以使用 /etc/shells: valid login shells 命令来显示所有的 shell,想要切换,直接输入 shell 名即可


    一、Shell 的启动

    bash 启动脚本是 bash 启动时会自动执行的脚本,因此用户可以把一些环境变量的设置和 alias、umask 设置等放到启动脚本中,这样每次启动 shell 时都会自动生效

    但是,启动 bash 的方法不同,执行启动脚本的步骤也不同


    1、作为交互登录 Shell 启动,或者使用 --login 参数启动

    交互 Shell 指的是用户在提示符下输入命令的 Shell,而不是执行脚本的 shell

    这样启动 bash 会自动执行以下脚本:

    1. 执行 /etc/profil,系统中的每个用户登录时都执行,只有管理员可以修改

    2. 然后依次执行当前用户主目录的 ~/.bash_profile、~/.bash_login 和 ~/.profile 三个文件(如果存在的话)

    3. 在Shell 退出时,会执行 ~/.bash_logout 脚本(如果存在的话)


    通常在 ~/.bash_profile 中会有下面几行:

    这样,如果 ~/.bashrc 文件存在,还会调用这个脚本


    2、以交互非登录 Shell 启动

    比如在图形界面下开一个终端窗口,或者在登录 Shell 提示符下再输入 bash 命令,就得到一个交互非登录的 shell

    这种 shell 在启动时自动执行 ~/.bashrc 脚本

    如上面所讲,如果要在启动脚本中做某些设置,使它在图形终端窗口和字符终端的Shell中都起作用,最好就是在 ~/.bashrc 中设置。


    如果终端或远程登录,那么登录 Shell 是该用户的所有其他进程的父进程,所以环境变量在登录 Shell 的启动脚本里设置一次就可以自动带到其他非登录 Shell 里,而本地变量、函数、 alias 等设置没有办法带到子Shell里,需要每次启动非登录Shell时设置一遍,所以就需要有非登录Shell的启动脚本,所以一般来说在 ~/.bash_profile 里设置环境变量,在 ~/.bashrc 里设置本地变量、函数、 alias 等。

    如果你的Linux带有图形系统则不能这样设置,由于从图形界面的窗口管理器登录并不会产生登录Shell,所以环境变量也应该在 ~/.bashrc 里设置。


    3、非交互式启动

    为了执行脚本而 fork 出来的子 Shell 是非交互式 Shell,启动时执行的脚本文件有环境变量 BASH_ENV 定义,相当于执行下面的命令:

              if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi


    4、以 sh 命令启动

    如果以 sh 命令启动 bash,bash 将模拟 sh 的行为

    如果作为交互登录 shell 启动,则会一次执行:

    1. /etc/profile

    2. ~/.profile


    如果作为交互式 Shell 启动,相当于执行

              if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi

    以 #! /bin/sh 开头的脚本就是这种情况,不会运行任何脚本


    二、Shell 如何执行命令

    1、执行交互式命令

    凡是使用 which 命令查不到程序文件所在位置的命令都是 shell 的内建命令,这些命令相当于 Shell 的进程中的一个函数,没有单独的 man 手册,可以使用下面的命令查看: man bash-builtins

    对于非内建命令,Shell 会 fork 并 exec 该命令,创建一个新的进程,但是对于内建命令则不会,如 cd、alias、umask、exit、export、shift、if、eval、[、for、while 等命令都是内建命令

    虽然内建命令不创建新的进程,但是也会有返回值,通常也用 0 表示调用成功,这个返回值被称为 Exit Status (状态码),可以使用特殊变量 $? 读出


    2、执行脚本

    shell 脚本中用 # 表示注释,相当于 C 语言中的 // 注释,但是#! 却表示该脚本使用 /bin/sh 解释执行,并不代表注释

    如下例是一个简单的 shell 脚本:

    将上面的代码保存在 .sh 文件中,即为一个 Shell 脚本

    执行脚本只需要输入命令:./script.sh,这是 sh ./script.sh 命令的简写


    执行上面的脚本的步骤为:


    三、Shell 的基本语法

    1、变量

    一般的,Shell 变量由全大写字母加下划线组成,有两种类型的 Shell 变量

    (1)环境变量

    环境变量可以从父进程传给子进程,因此 Shell 进程的环境变量可以从当前的 Shell 进程传给 fork 出来的子进程,使用 printenv 命令可以显示当前 Shell 进程的环境变量


    (2)本地变量

    只存在于当前 Shell 进程的变量,用set命令可以显示当前 Shell 进程中定义的所有变量(包括本地变量和环境变量)和函数


    • set

    显示当前所有变量和函数


    • 变量的定义

    环境变量是任何进程都有的概念,而本地变量是 Shell 所特有的概念,在 Shell 中,环境变量和本地变量的定义和用法相似,在 Shell 中定义或赋值一个变量可以使用下面的格式:

               VARNAME=value

    等号两边是不能有空格的,否则就会被解释成命令或命令行参数


    • export -- 将变量导出为环境变量

    任何一个变量定义后都仅存在于当前的 Shell 进程,是本地变量,用 export 命令可以把本地变量导出为环境变量,定义和导出环境变量也可以一步完成

               export VARNAME=value

    当然也可以分两步:

               VARNAME=value

               export VARNAME


    • unset -- 删除变量

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

               unset VARNAME


    • echo -- 显示变量的值

    使用 echo 命令可以显示变量的值


    一般对于 VARNAME 变量,我们使用 ${VARNAME} 表示他的值,在不引起歧义的情况下,我们也可以直接使用 VARNAME 表示他的值


    Shell 中的所有变量都是字符串,Shell中的变量也不需要先定义后使用,使用一个没有定义的变量,这个变量的值为空字符串


    2、通配符 -- *、?、[]

    Shell 中也有通配符,如下表:

    如我们可以使用 ls  ch0[012].doc 命令查找文件,如果当前目录下有 ch00.doc 和 ch02.doc,ls 的参数会直接转换成这两个文件名,而不是一个匹配字符串


    3、命令代换 -- `或 $()

    由反引号(键盘上 ESC 键下面的,主键盘区左上角·/~)所引起来的也是一条命令,Shell 会首先执行反引号中的命令,然后将结果代换到原来的位置进行原命令的执行,如下面的命令:

               DATE=`date`

               echo $DATE

    反引号和 $() 是一样的:

               DATE=$(date)

               echo $DATE


    4、算术代换 -- $(())

    Shell 会将 $(()) 中的 Shell 变量的取值转换成整数用于算术计算(其他情况下 Shell 都将变量视为字符串,无法进行算术计算)

               VAR=45

               echo $(($VAR+3))

    会显示 48

    $(()) 中只能进行 +、-、*、/ 和 () 运算,并且只能进行整数运算


    5、转义字符 -- \

    和 C 语言一样,Shell 中也需要转义字符,如 \ 、\$、\\、\`、\"


    6、字符串 -- '、"

    在 Shell 中单引号中的所有字符都被认为是普通的字符,所以不需要转义字符,如运行:

               $ echo '$SHELL'

    会显示 $SHELL

               $ echo 'ABC\\'

    会显示 ABC\\


    双引号也将其中的字符串视为字面值,但是反引号、$、转义字符等等都保持原来的意义

    如:

               $ echo "$SHELL"

    会显示 /bin/bash


               $echo "`date`"

    会显示 Sun Apr 20 11:22:06 CEST 2003


    五、Shell 脚本语法

    1、条件测试 -- test、[]

    命令 test 或 [] 可以测试一个条件是否城里,如果测试结果为真,则该命令的 Exit Status 为0,如果测试结果为假,则命令的 Exit Status 为1(与C语言中正好相反)

    由于 [] 中的 [ 实际上是一个命令,他后面的都是这个命令的参数,因此需要用空格隔开

    如下例:


    常见测试命令如下所示:


    与 C 语言类似,测试条件之间还可以做与、或、非逻辑运算


    如:


    需要注意的是,如果上例中的 VAR 变量没有被预先定义,那么就会被解释器展开为空字符串,整个命令就变成了:

               [ -d Desktop -a = 'abc' ] 

    这就成了一个语法错误

    为了避免这样的意外情况发生,一个好的 Shell 编程习惯总是把变量取值放到双引号之中:


    这样,虽然 VAR 没有被预先定义,但是命令还是被展开成了

                [ -d Desktop -a "" = 'abc' ]


    2、分支控制 -- if、then、elif、else、fi

    和 C 语言类似,在 Shell 中使用 if、then、elif、else、fi 几个命令实现分支控制,例如:

               if [ -f ~/.bashrc ]; then

                          .~/.bashrc

               fi


    • :

    : 是一个特殊的指令,称为“空命令”,该命令不做任何事,但是 Exit Status 总是真,也可以使用 /bin/true 或 /bin/false 获得总是真或假的 Exit Status

               if :; then echo "always true"; fi

    与下面的例子是一样的:

               if /bin/true; then echo "always true"; fi


    • read

    我们也可以使用 read 命令等待用户键入一行字符串,存到一个 Shell 变量中


    • &&、||

    与C语言类似,Shell 也提供 && 与 ||

              test "$VAR" -gt 1 -a "$VAR" -lt 3

    等价于

              test "$VAR" -gt 1 && test "$VAR" -lt 3


    因为 && 操作的短路求值特性,很多 Shell 脚本喜欢写成:

             test "$(whoami)" != 'root' && (echo you are using a nonprivileged account; exit 1)


    3、case、esac

    case 命令类似于 C 语言的 switch/case 语句,esac 用来标志 case 语句块的结束

    Shell 中的 case 语句不仅可以用来匹配数字,也可以用来匹配字符串和通配符


    如下例,每个匹配分支都可以有若干条命令,末尾必须以;;结束


    下面的例子常常用在某服务的处理上,比如对于apache,我们可以输入 apache start 这个命令启动他,也可以使用 apache stop 这个命令终止他


    $0、$1 ... 等等指的是取命令的第0个参数、第1个参数。。。


    4、for、do、done

    Shell 脚本的 for 循环结构和 C 语言很不一样,他类似于某些编程语言的 foreach 循环

    如下面的例子:


    例子中,FRUIT 是一个变量,让这个变量依次取值为 apple、banana、pear 做循环,done 用来标志循环结束


    如果目录下有 chap0、chap1、chap2 等文件,下面的循环将他们重命名为 chap0~ 、 chap1~ 、 chap2~ 等

              $ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done


    5、while、do、done

    while 的用法和 C 语言非常类似,比如下面是一个验证密码的脚本:


    我们也可以像 C 语言中那样控制 while 循环的循环次数


    6、一些特殊的变量

    有很多变量是被 Shell 自动赋值的,如下表


    参数 $n 被称为“位置参数”

    • shift

    shift 命令可以令位置参数左移,比如 shift 3 表示让 $4 变成 $1,$5 变成 $2,原来的 $1、$2就会被丢弃掉,而 $0 不移动

    不带参数的 shift 命令相当于 shift 1


    7、函数

    Shell 中的函数定义中没有返回值也没有参数列表

    如下面例子所示:


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

    Shell脚本中的函数必须先定义后调用,一般把函数定义都写在脚本的前面,把函数调用和其它命令写在脚本的最后(类似C语言中的 main 函数,这才是整个脚本实际开始执行命令的地方)。


    Shell函数没有参数列表并不表示不能传参数,事实上,函数就像是迷你脚本,调用函数时可以传任意个参数,在函数内同样是用 $0 、 $1 、 $2 等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的 $0 、 $1 、 $2 等变量。函数中可以用 return 命令返回,如果 return 后面跟一个数字则表示函数的Exit Status。


    六、Shell 脚本的调试方法

    Shell提供了一些用于调试脚本的选项:

    • -n

    读一遍脚本中的命令但是不执行,用于检查脚本中的语法错误

    • -v

    一边执行脚本,一边将执行过的脚本命令打印到标准错误输出

    • -x

    提供跟踪执行信息,将执行的每一条命令和结果依次打印出来


    有三种方法使用这些选项:

    1. 在命令行提供参数   $ sh -x ./script.sh

    2. 在脚本开头提供参数    #! /bin/sh -x

    3. 在脚本中用 set 命令启用或禁用参数



    下面是我写的一个脚本,在打开终端的时候给出人性化的提示,并且询问是否将虚拟机中的重要文件保存在宿主机上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #
    # file: .init.sh
    # author: 龙泉居士
    # date: 2013-05-06 18:51
    #
       
    echo "\033[44m`date`\033[0m"
    echo "\033[44mHello zeyu!\033[0m"
    echo ""
       
    SAVE_OR_NOT="no"
    read -p "Do you want to save the Workspace [yes/no](no): " SAVE_OR_NOT
    if "$SAVE_OR_NOT" "yes" ];
    then sh ~/Workspace/.save.sh 
    fi
    exit 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #
    # file: .save.sh
    # author: 龙泉居士
    # date: 2013-05-06 18:52
    #
       
    cd ~/Workspace
    mysqldump -u root -p150102 zeyu_student > sql/zeyu_student.sql
    mysqldump -u root -p150102 zeyu_custom > sql/zeyu_custom.sql
    tar -cvf Workspace.tar *
    mv Workspace.tar /mnt/hgfs/Debin/

    然后在~/.bashrc 中添加下面的代码

    1
    2
    3
    4
    # added by zeyu 2013/05/06
    cd Workspace
    sh ~/Workspace/.init.sh
    export PATH=$PATH:/home/zeyu/Workspace
    展开全文
  • shell极简教程

    2017-10-11 01:29:16
    一,shell题记 不懂shell的程序员不是好程序员,学习shell是为了自动化,使用自动化可以非常有效的提高工作效率。没有一个公司不要求linux的基本技能的,只是不同岗位要求掌握的程度不同。 二,shell简介 ...

    一,shell题记

    不懂shell的程序员不是好程序员,学习shell是为了自动化,使用自动化可以非常有效的提高工作效率。没有一个大公司不要求linux的基本技能的,只是不同岗位要求掌握的程度不同。

    二,shell简介

    Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

    它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Unix/Linux系统的关键。

    可以说,shell使用的熟练程度反映了用户对Unix/Linux使用的熟练程度。

    Shell有两种执行命令的方式:

    交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。

    批处理(Batch):用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。

    Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。

    Shell初学者请注意,在平常应用中,建议不要用 root 帐号运行 Shell 。作为普通用户,不管您有意还是无意,都无法破坏系统;但如果是 root,那就不同了,只要敲几个字母,就可能导致灾难性后果。

    三,几种常见的shell

    上面提到过,Shell是一种脚本语言,那么,就必须有解释器来执行这些脚本。

    Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等,习惯上把它们称作一种Shell。我们常说有多少种Shell,其实说的是Shell脚本解释器。

    bash:bash是Linux标准默认的shell,本教程也基于bash讲解。bash由Brian Fox和Chet Ramey共同完成,是BourneAgain Shell的缩写,内部命令一共有40个。

    Linux使用它作为默认的shell是因为它有诸如以下的特色:

    可以使用类似DOS下面的doskey的功能,用方向键查阅和快速输入并修改命令。

    自动通过查找匹配的方式给出以某字符串开头的命令。

    包含了自身的帮助功能,你只要在提示符下面键入help就可以得到相关的帮助。

    sh:sh 由Steve Bourne开发,是Bourne Shell的缩写,sh 是Unix 标准默认的shell。

    ash:ash shell 是由Kenneth Almquist编写的,Linux中占用系统资源最少的一个小shell,它只包含24个内部命令,因而使用起来很不方便。

    csh:csh 是Linux比较大的内核,它由以William Joy为代表的共计47位作者编成,共有52个内部命令。该shell其实是指向/bin/tcsh这样的一个shell,也就是说,csh其实就是tcsh。

    ksh:ksh 是Korn shell的缩写,由Eric Gisin编写,共有42条内部命令。该shell最大的优点是几乎和商业发行版的ksh完全兼容,这样就可以在不用花钱购买商业版本的情况下尝试商业版本的性能了。

    注意:bash是 Bourne Again Shell 的缩写,是linux标准的默认shell ,它基于Bourne shell,吸收了C shell和Korn shell的一些特性。bash完全兼容sh,也就是说,用sh写的脚本可以不加修改的在bash中执行。

    四,编程型和解释型语言的区别

    大体上,可以将程序设计语言可以分为两类:编译型语言和解释型语言。

    编译型语言

    很多传统的程序设计语言,例如Fortran、Ada、Pascal、C、C++和Java,都是编译型语言。这类语言需要预先将我们写好的源代码(source code)转换成目标代码(object code),这个过程被称作“编译”。

    运行程序时,直接读取目标代码(object code)。由于编译后的目标代码(object code)非常接近计算机底层,因此执行效率很高,这是编译型语言的优点。

    但是,由于编译型语言多半运作于底层,所处理的是字节、整数、浮点数或是其他机器层级的对象,往往实现一个简单的功能需要大量复杂的代码。例如,在C++里,就很难进行“将一个目录里所有的文件复制到另一个目录中”之类的简单操作。

    解释型语言

    解释型语言也被称作“脚本语言”。执行这类程序时,解释器(interpreter)需要读取我们编写的源代码(source code),并将其转换成目标代码(object code),再由计算机运行。因为每次执行程序都多了编译的过程,因此效率有所下降。

    使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象;缺点是它们的效率通常不如编译型语言。不过权衡之下,通常使用脚本编程还是值得的:花一个小时写成的简单脚本,同样的功能用C或C++来编写实现,可能需要两天,而且一般来说,脚本执行的速度已经够快了,快到足以让人忽略它性能上的问题。脚本编程语言的例子有awk、Perl、Python、Ruby与Shell。

    五,什么时候使用shell?

    因为Shell似乎是各UNIX系统之间通用的功能,并且经过了POSIX的标准化。因此,Shell脚本只要“用心写”一次,即可应用到很多系统上。因此,之所以要使用Shell脚本是基于:

    简单性:Shell是一个高级语言;通过它,你可以简洁地表达复杂的操作。

    可移植性:使用POSIX所定义的功能,可以做到脚本无须修改就可在不同的系统上执行。

    开发容易:可以在短时间内完成一个功能强大又妤用的脚本。

    但是,考虑到Shell脚本的命令限制和效率问题,下列情况一般不使用Shell:

    资源密集型的任务,尤其在需要考虑效率时(比如,排序,hash等等)。

    需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用C++或FORTRAN 来处理)。

    有跨平台(操作系统)移植需求(一般使用C 或Java)。

    复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)。

    对于影响系统全局性的关键任务应用。

    对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵、破解、恶意破坏等等。

    项目由连串的依赖的各个部分组成。

    需要大规模的文件操作。

    需要多维数组的支持。

    需要数据结构的支持,比如链表或数等数据结构。

    需要产生或操作图形化界面 GUI。

    需要直接操作系统硬件。

    需要 I/O 或socket 接口。

    需要使用库或者遗留下来的老代码的接口。

    私人的、闭源的应用(shell 脚本把代码就放在文本文件中,全世界都能看到)。

    如果你的应用符合上边的任意一条,那么就考虑一下更强大的语言吧——或许是Perl、Tcl、Python、Ruby——或者是更高层次的编译语言比如C/C++,或者是Java。即使如此,你会发现,使用shell来原型开发你的应用,在开发步骤中也是非常有用的。

    六,第一个shell脚本

    打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好,如果你用php写shell 脚本,扩展名就用php好了。

    输入一些代码:

    #!/bin/bashecho "Hello World !"

    “#!” 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell。echo命令用于向窗口输出文本。

    运行Shell脚本有两种方法。

    作为可执行程序

    将上面的代码保存为test.sh,并 cd 到相应目录:

    chmod +x ./test.sh #使脚本具有执行权限./test.sh #执行脚本

    注意,一定要写成./test.sh,而不是test.sh。运行其它二进制的程序也一样,直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里,所以写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找。

    通过这种方式运行bash脚本,第一行一定要写对,好让系统查找到正确的解释器。

    这里的"系统",其实就是shell这个应用程序(想象一下Windows Explorer),但我故意写成系统,是方便理解,既然这个系统就是指shell,那么一个使用/bin/sh作为解释器的脚本是不是可以省去第一行呢?是的。

    作为解释器参数

    这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

    /bin/sh test.sh/bin/php test.php

    这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

    再看一个例子。下面的脚本使用 read 命令从 stdin 获取输入并赋值给 PERSON 变量,最后在 stdout 上输出:

    #!/bin/bash# Author : mozhiyan# Copyright (c) http://see.xidian.edu.cn/cpp/linux/# Script follows here:echo "What is your name?"read PERSON

    echo "Hello, $PERSON"

    运行脚本:

    chmod +x ./test.sh

    $./test.sh

    What is your name?

    mozhiyan

    Hello, mozhiyan

    七,shell变量

    Shell支持自定义变量。

    定义变量

    定义变量时,变量名不加美元符号($),如:

    variableName="value"

    注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

    首个字符必须为字母(a-z,A-Z)。

    中间不能有空格,可以使用下划线(_)。

    不能使用标点符号。

    不能使用bash里的关键字(可用help命令查看保留关键字)。

    变量定义举例:

    myUrl="http://see.xidian.edu.cn/cpp/linux/"myNum=100

    使用变量

    使用一个定义过的变量,只要在变量名前面加美元符号($)即可,如:

    your_name="mozhiyan"echo $your_name

    echo ${your_name}

    变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

    for skill in Ada Coffe Action Java

    do

    echo "I am good at ${skill}Script"done

    如果不给skill变量加花括号,写成echo "I am good atskillScript",解释器就会把skillScript",解释器就会把skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

    推荐给所有变量加上花括号,这是个好的编程习惯。

    重新定义变量

    已定义的变量,可以被重新定义,如:

    myUrl="http://see.xidian.edu.cn/cpp/linux/"echo ${myUrl}

    myUrl="http://see.xidian.edu.cn/cpp/shell/"echo ${myUrl}

    这样写是合法的,但注意,第二次赋值的时候不能写myUrl="http://see.xidian.edu.cn/cpp/shell/",使用变量的时候才加美元符(myUrl="http://see.xidian.edu.cn/cpp/shell/",使用变量的时候才加美元符()。

    只读变量

    使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

    下面的例子尝试更改只读变量,结果报错:

    #!/bin/bashmyUrl="http://see.xidian.edu.cn/cpp/shell/"readonly myUrl

    myUrl="http://see.xidian.edu.cn/cpp/danpianji/"

    运行脚本,结果如下:

    /bin/sh: NAME: This variable is read only.

    删除变量

    使用 unset 命令可以删除变量。语法:

    unset variable_name

    变量被删除后不能再次使用;unset 命令不能删除只读变量。

    举个例子:

    #!/bin/shmyUrl="http://see.xidian.edu.cn/cpp/u/xitong/"unset myUrl

    echo $myUrl

    上面的脚本没有任何输出。

    变量类型

    运行shell时,会同时存在三种变量:

    1) 局部变量

    局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

    2) 环境变量

    所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

    3) shell变量

    shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

    八,shell特殊变量

    前面已经讲到,变量名只能包含数字、字母和下划线,因为某些包含其他字符的变量有特殊含义,这样的变量被称为特殊变量。

    例如,$ 表示当前Shell进程的ID,即pid,看下面的代码:

    $echo $$

    运行结果:29949

    特殊变量列表

    变量含义

    $0当前脚本的文件名

    $n传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是1,第二个参数是1,第二个参数是2。

    $#传递给脚本或函数的参数个数。

    $*传递给脚本或函数的所有参数。

    $@传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。

    $?上个命令的退出状态,或函数的返回值。

    $$当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

    九,shell替换

    如果表达式中包含特殊字符,Shell 将会进行替换。例如,在双引号中使用变量就是一种替换,转义字符也是一种替换。

    举个例子:

    #!/bin/basha=10echo -e "Value of a is $a \n"

    运行结果:

    Value of a is 10

    这里 -e 表示对转义字符进行替换。如果不使用 -e 选项,将会原样输出:

    Value of a is 10\n

    下面的转义字符都可以用在 echo 中:

    转义字符含义

    \\反斜杠

    \a警报,响铃

    \b退格(删除键)

    \f换页(FF),将当前位置移到下页开头

    \n换行

    \r回车

    \t水平制表符(tab键)

    \v垂直制表符

    可以使用 echo 命令的 -E 选项禁止转义,默认也是不转义的;使用 -n 选项可以禁止插入换行符。

    命令替换

    命令替换是指Shell可以先执行命令,将输出结果暂时保存,在适当的地方输出。

    命令替换的语法:

    `command`

    注意是反引号,不是单引号,这个键位于 Esc 键下方。

    下面的例子中,将命令执行结果保存在变量中:#!/bin/bashDATE=`date`

    echo "Date is $DATE"USERS=`who | wc -l`

    echo "Logged in user are $USERS"UP=`date ; uptime`

    echo "Uptime is $UP"

    运行结果:

    Date is Thu Jul  2 03:59:57 MST 2009Logged in user are 1Uptime is Thu Jul  2 03:59:57 MST 2009

    03:59:57 up 20 days, 14:03,  1 user,  load avg: 0.13, 0.07, 0.15

    变量替换

    变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值。

    可以使用的变量替换形式:

    形式说明

    ${var}变量本来的值

    ${var:word}如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值。

    ${var:=word}如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。

    ${var:?message}如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。

    若此替换出现在Shell脚本中,那么脚本将停止运行。

    ${var:+word}如果变量 var 被定义,那么返回 word,但不改变 var 的值。

    请看下面的例子:

    #!/bin/bash echo ${var:-"Variable is not set"}

    echo"1 - Value of var is ${var}"echo ${var:="Variable is not set"}

    echo"2 - Value of var is ${var}"unset var

    echo ${var:+"This is default value"}

    echo"3 - Value of var is $var"var="Prefix"echo ${var:+"This is default value"}

    echo"4 - Value of var is $var"echo ${var:?"Print this message"}

    echo"5 - Value of var is ${var}"

    运行结果:

    Variable is not set1 - Value of var isVariable is not set2 - Value of var is Variable is not set3 - Value of var isThis is default value4 - Value of var is Prefix

    Prefix5 - Value of var is Prefix

    十,shell运算符

    Bash 支持很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试运算符。

    原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。

    expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

    例如,两个数相加:

    #!/bin/bashval=`expr 2 + 2`

    echo "Total value : $val"

    运行脚本输出:

    Total value : 4

    两点注意:

    表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。

    完整的表达式要被 ` ` 包含,注意这个字符不是常用的单引号,在 Esc 键下边。

    算术运算符

    先来看一个使用算术运算符的例子:

    #!/bin/sh a=10 b=20

    val=`expr $a + $b`

    echo "a + b : $val"

    运行结果:

    a + b : 30

    注意:

    乘号(*)前边必须加反斜杠(\)才能实现乘法运算;

    if...then...fi 是条件语句,后续将会讲解。

    算术运算符列表

    运算符说明举例

    +加法`expra+b` 结果为 30。

    -减法`expra−b` 结果为 10。

    *乘法`expra\**b` 结果为  200。

    /除法`exprb/a` 结果为 2。

    %取余`exprba` 结果为 0。

    =赋值a=$b 将把变量 b 的值赋给 a。

    ==相等。用于比较两个数字,相同则返回 true。[a==b ] 返回 false。

    !=不相等。用于比较两个数字,不相同则返回 true。[a!=b ] 返回 true。

    注意:条件表达式要放在方括号之间,并且要有空格,例如 [a ==b]是错误的,必须写成 [ a ==b]。

    关系运算符

    关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

    先来看一个关系运算符的例子:

    #!/bin/sh a=10 b=20

    if [ $a -eq $b ]

    then

    echo "$a -eq $b : a is equal to b"

    else echo "$a -eq $b: a is not equal to b"

    运行结果:

    10 -eq 20: a is not equal to b

    关系运算符列表

    运算符说明举例

    -eq检测两个数是否相等,相等返回 true。[a−eqb ] 返回 true。

    -ne检测两个数是否相等,不相等返回 true。[a−neb ] 返回 true。

    -gt检测左边的数是否大于右边的,如果是,则返回 true。[a−gtb ] 返回 false。

    -lt检测左边的数是否小于右边的,如果是,则返回 true。[a−ltb ] 返回 true。

    -ge检测左边的数是否大等于右边的,如果是,则返回 true。[a−gea−geb ] 返回 false。

    -le检测左边的数是否小于等于右边的,如果是,则返回 true。[a−leb ] 返回 true。

    布尔运算符

    先来看一个布尔运算符的例子:

    #!/bin/sh a=10 b=20 if [ $a != $b ]

    then

    echo "$a != $b : a is not equal to b"

    else echo "$a != $b: a is equal to b"

    运行结果:

    10 != 20 : a is not equal to b

    布尔运算符列表

    运算符说明举例

    !非运算,表达式为 true 则返回 false,否则返回 true。[ ! false ] 返回 true。

    -o或运算,有一个表达式为 true 则返回 true。[a−lt20-ob -gt 100 ] 返回 true。

    -a与运算,两个表达式都为 true 才返回 true。[a−lt20−ab -gt 100 ] 返回 false。

    字符串运算符

    先来看一个例子:

    #!/bin/sh a="abc" b="efg" if [ $a = $b ]

    then

    echo "$a = $b : a is equal to b" else

    echo "$a = $b: a is not equal to b"

    运行结果:

    abc = efg: a is not equal to b

    字符串运算符列表

    运算符说明举例

    =检测两个字符串是否相等,相等返回 true。[a=b ] 返回 false。

    !=检测两个字符串是否相等,不相等返回 true。[a!=b ] 返回 true。

    -z检测字符串长度是否为0,为0返回 true。[ -z $a ] 返回 false。

    -n检测字符串长度是否为0,不为0返回 true。[ -z $a ] 返回 true。

    str检测字符串是否为空,不为空返回 true。[ $a ] 返回 true。

    文件测试运算符

    文件测试运算符用于检测 Unix 文件的各种属性。

    例如,变量 file 表示文件“/var/www/tutorialspoint/unix/test.sh”,它的大小为100字节,具有 rwx 权限。下面的代码,将检测该文件的各种属性:

    #!/bin/sh file="/var/www/tutorialspoint/unix/test.sh"

    if [ -r $file ]

    then

    echo "File has read access"

    else echo "File does not have read access"

    运行结果:

    File has read access

    文件测试运算符列表

    操作符说明举例

    -b file检测文件是否是块设备文件,如果是,则返回 true。[ -b $file ] 返回 false。

    -c file检测文件是否是字符设备文件,如果是,则返回 true。[ -b $file ] 返回 false。

    -d file检测文件是否是目录,如果是,则返回 true。[ -d $file ] 返回 false。

    -f file检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。[ -f $file ] 返回 true。

    -g file检测文件是否设置了 SGID 位,如果是,则返回 true。[ -g $file ] 返回 false。

    -k file检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。[ -k $file ] 返回 false。

    -p file检测文件是否是具名管道,如果是,则返回 true。[ -p $file ] 返回 false。

    -u file检测文件是否设置了 SUID 位,如果是,则返回 true。[ -u $file ] 返回 false。

    -r file检测文件是否可读,如果是,则返回 true。[ -r $file ] 返回 true。

    -w file检测文件是否可写,如果是,则返回 true。[ -w $file ] 返回 true。

    -x file检测文件是否可执行,如果是,则返回 true。[ -x $file ] 返回 true。

    -s file检测文件是否为空(文件大小是否大于0),不为空返回 true。[ -s $file ] 返回 true。

    -e file检测文件(包括目录)是否存在,如果是,则返回 true。[ -e $file ] 返回 true。




    架构师小秘圈,聚集10万架构师的小圈子!不定期分享技术干货,行业秘闻!汇集各类奇妙好玩的话题和流行动向!长按左侧图片扫码加入微信群!

    展开全文
  • shell脚本如何方便地处理JSON格式的数据呢,这里介绍一个工具: jq 。 官方教程 简单翻译如下。 1、获取JSON数据 我们以github上jq项目最新5条评论的JSON数据为例。获取数据如下: curl 'https://api
  • 最好的shell入门教程

    2013-06-21 16:13:36
    从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁。用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操作。在Linux GUI日益完善...
  • 在使用linux虚拟机处理数据的过程中,由于需要处理的文件量较,并且有大量重复的操作,为了省时省力,发现可以使用shell脚本一次处理大量的文件。 1、shell文件创建 命令行输入touch XXX.sh 2、文件编辑 ...
  • Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为...
  • shell 脚本教程 入门级

    2015-05-19 14:44:18
     Linux中有好多中不同的shell,但是通常我们使用bash (bourne again shell) 进行shell编程,因为bash是免费的并且很容易使用。所以在本文中笔者所提供的脚本都是使用bash(但是在大多数情况下,这些脚本同样可以在 ...
  • 一个很不错的bash脚本编写教程,至少没接触过BASH的也能看懂建立一个脚本 Linux中有好多中不同的shell,但是通常我们使用bash (bourne again shell) 进行shell编程,因为bash是免费的并且很容易使用。所以在本文中...
  • 在公司项目的开发过程中,需要编写shell脚本去处理一个业务,在编写过程中发现自身对shell脚本的知识不够完善,顾整理一下,本文章主要内容来自菜鸟教程 , 也添加了一些知识点 shell脚本? 在说什么是shell脚本之前...
  • 用了caffe有一段时间了,感觉自己写shell脚本的能力有待提高,特地从菜鸟笔记处系统的看了一遍,其实学习基础就可,内容也不多,我就不总结了。把网站上的内容用markdown重新编辑了一下,各位可以根据需要看看;参考...
  • Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为...
  • linux shell教程(一)

    2015-05-26 23:49:24
    教程摘自 C语言教程网 1.Shell简介:什么是ShellShell命令的两种执行方式 Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的部分工作都是通过Shell完成的。Shell既是一种命令语言,又...
  • Scrapy Shell的使用教程

    2017-07-10 14:46:15
    Scrapy shellScrapy shell是一个交互终端在未启动spider的情况下尝试及调试您的爬取代码。 其本意是用来测试提取数据的代码,不过您可以将其作为正常的Python终端,在上面测试任何的Python代码。该终端是用来测试...
  • 本文是Linux Shell系列教程的第(十一)篇,更多Linux Shell教程请看:Linux Shell系列教程 在上一篇Linux Shell系列教程之(十)Shell for循环中,我们已经对Shell 循环语句的for循环进行了介绍,本篇给大家介绍...
  • shell编程简明教程

    2016-04-08 18:08:53
    用户登录Linux后,就会出现一个系统提示符号,可以在符号后输入一堆命令,并获得预期效果,到底幕后是谁在帮我们处理这一堆事情呢?就是Shell,当用户登录时,实际上是进入到一个叫Shell的程序中。 Shell担任了...
  • Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为...
  • 嵌入式开源项目精选专栏 本专栏由Mculover666创建,主要内容为寻找嵌入式领域内的优质开源项目,一是帮助开发者使用开源项目实现更多的功能,二是通过这些开源项目,学习大佬的代码及背后的实现思想,提升自己的代码...
1 2 3 4 5 ... 20
收藏数 27,815
精华内容 11,126