bash学习笔记

bash学习笔记

说在前头

近期需要写一些bash的脚本来进行一些批量的任务,但由于上次自己写脚本还是在大概半年之前,因此有许多细节已经模糊不清了。其实这种现象之前也有过,比如在我学习使用正则表达过程中,学习过程的确不难,但是忘的也快,毕竟人脑不是电脑。如果不是经常使用的话的确是容易遗忘的(使用linux,以及写bash脚本仅仅是个人的偶尔需求,并非工作或是相关领域的学生),因此想写此笔记进行。对于bash的一些语法和命令进行精炼,加上自己的理解,整理成笔记。

我学习以及参考的主要书籍《Linux命令行与shell脚本编程大全》第三版Richard Blum Chrisrine Bresnahan著。如果作为第一次接触bash的同学可以仔细阅读相关书籍进行详细的学习,而我的文章作为自己理解和精炼,并定有缩略甚至可能出现一些错误。望周知。

十一章 构建基本脚本

  1. 使用多个命令

    在bash中,如果想要在一行中使用多个命令。这个和我们熟悉的C语言是相似的,只要在各个不同命令之间使用;(分号)即可。例如

    cd /home/; mkdir Mydocument
    

  1. 创建脚本文件

    我们在创建脚本过程中第一行往往是一句较为特殊的一行代码

    #!/bin/bash
    

    在bash中一般情况下#字符代表注释,因此后面可以写任何字符不影响脚本的运行,但是若是#!,则变成了特殊情况,这代表这个脚本使用/bin/bash这个程序运行。此时若要运行该脚本只需要如下命令即可.

    ./test.sh
    

    但是在我的计算机的bash中,不论是否有#!/bin/bash都可以使用上面的命令来运行。想来bash已经将其默认为一个可执行的ASCII文本默认为bash脚本语言。但是如果我们编写其他语言的文件时一定要加上相应的解释器,如:

    #!/bin/python3
    print ("Hello")
    

    上面创建一个名为hello.py的文件,如果你想要./hello.py这样来执行文件,头一行必须加上相应的解释器。

    上述前提:不论是test.sh还是hello.py都具有可执行的权限,如果没有需要使用chmod命令进行更改


  1. 显示消息

    这个命令和C语言中的printf,和C++中的cout,和python中的print的最基本功能是相似的,简而言之就是输出一段文字,但要注意的是echo输出的是一段标准输出,标准输出可作为一些参数输入到其他命令当中

    echo Hello world!!
    

    其他更多使用方法可以查询man手册


  1. 使用变量

    bash脚本中的变量不像其他语言中一样丰富,在我眼里,这其中只有一种字符串类型的变量,当然后续还有一些数字计算,以及数组等,但是数字计算需要使用expr命令或者是中括号,使用了“第三方”工具来进行计算,不够原生。另外数组中的内容也尽是字符串类型数据,因此本质上来说都是字符串类型。

    • 环境变量

      如果对linux有些了解的话那环境变量一定不陌生。常用的环境变量名称常用纯大写字母来表示,系统的正常运行依赖无数的环境变量,比如说最熟悉的PATH变量。

      其中PATH相当于变量名称,而如果要查看变量内容

      echo ${PATH}
      输出:
      /usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:
      

      这个过程我们是想要吧变量PATH的内容输出到屏幕上以供我们人眼所看到,因此我们使用echo标准输出命令,而如果我们。

       echo PATH
      

      系统会认为我们想要输出的是PATH这四个字符,但是我们需要的是PATH变量的内容,因此需要特殊的符号来代表PATH变量的内容${PATH}

      如果我们思考为什么python或者是C中输出变量内容为什么没有需要特殊的符号来标记呢?这是因为C或python中有丰富的数据类型。

      int a = 3;
      cout << a;
      

      由于这个过程已经指定了a变量为int类型,计算机并不会认为你想要输出一个a字符。总之C或者python中输出字符串还是变量并不会产生歧义,但是在bash中单纯地echo 变量名是会产生歧义的。

      最后说一点变量中的大括号是可以省略的,echo $PATH,也是能够正确输出变量内容的,不过要小心,这其中还是有些坑我们后面会说明。Anyway,${PATH}这种写法准没错,不嫌弃麻烦还是这种写法。

    • 用户变量

      简单来说用户自己创建的临时变量

      #!/bin/bash
      var=hello
      echo ${var}
      var="${var} World!!"
      echo ${var}
      echo "${var}"
      echo '${var}'
      输出:
      hello
      hello World!!
      hello World!!
      ${var}
      

      在这里只是想要说明一些bash中的变量修改比较简单粗暴,由于没有数据类型的困扰,只需要在使用=来赋予或者添加变量内容。另外记住${变量名}代表的变量内容,不论是在双引号里还是在方括号(方括号我们后续会使用到)里,唯一例外就是在单引号内,单引号像是照妖镜,让一切现真身!!

    • 变量替换

      非常有用 ,常用有两种形式,反引号或者是$(命令),以date,显示日期命令为例子。

      var1=$(date +%y%m%d)
      var2=`date +%y%m%d`
      echo ${var1}
      echo ${var2}
      输出:
      210221
      210221
      

      两种输出无区别,仅仅是个人习惯问题。


  1. 重定向输入和输出

    • 输出重定向

      输出重定向有两个符号>和>>。但箭头>可以将标准输出定向到一个文件中,并且覆盖源文件内容。双箭头>>可以将标准输出定向到一个文件中,不覆盖源文件内容,或是说在源文件内容基础上添加内容

      dmesg > test
      ls -l >> test
      date > test
      
    • 输入重定向

      与输出重定向相对应,输出重定向是将命令的内容输出到一个文件中,而输入重定向一般是将文件的内容输入到命令当中。

      比如一般情况下,我们查看该目录下文件和文件夹的总数常用ls -l | wc -l,这其中将ls -l命令的输出内容输入到wc -l中来计数一共有多少行文字。但是我们也可以使用输入重定向将一个文本内容输入到wc -l中来计算一共有多少行文字

      ls -l > test
      wc -l < test
      

      上面输出的结果与ls -l | wc -l完全一致

    • 管道命令

      管道命令也是使用的相当多的命令,命令形式是command1 | command2,管道命令就是中间的竖线,将命令1的标准输出内容作为命令2的输入内容。对于我来说最常用的形式便是查找文件,比如在一个目录下我想要找一个文件,但是忘记了文件的具体名称,只记得是一个rar压缩包

      ls -l | grep -E rar$
      

  1. 执行数学运算

    在编写脚本过程中我们难免需要一些数学运算,整数的加减法,浮点数或是说小数的加减法,书上总共列了几种方法,但我认为最后一种方法已经涵盖了之前的数学运算的所有能力,因此我只记住,也一直在用的是书中提到的最后一种方法。

    使用bc,linux的内置计算器,对于浮点数来说主要记住scale=n,保留n位小数的选项,比如

    echo "scale=5; 3.14 / 2.22" | bc
    输出:
    1.41441
    

    如果没有scale,则计算默认保留整数。

    当然比较大小也可以计算

    echo "3.14 <= 2.22" | bc
    输出:
    0
    

  1. 退出脚本

    这里介绍的这个应该属于地一个特殊变量$?,其中的美元号和一般变量中是同一个${变量名},只是变量名变成特殊的?。该命令表示上一个命令结束状态。相应的编号如下。

    asda
    输出:
    bash: asda:未找到命令
    echo $?
    输出:
    127  
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWwG6Ydt-1614520145294)(/home/einstein/.config/Typora/typora-user-images/image-20210221180115545.png)]

    在书写脚本过程中我们可以自己制定相应的推出状态码,使用exit,比如想要另脚本退出的状态码为上图中没有的数字32,可以在脚本最后添加exit 32命令即可。


十二章 使用结构化命令

  1. if 判断语句

    • if-then语句

      if commands
      then
      	commands
      fi
      

      这个是判断语句最基本的用法,then的位置无关紧要,也可以写成

      if commands then
      	commands
      fi
      

      这就是脚本中的判断语句,但是需要注意的是if后面的内容是一个命令判断的结果依照该命令的退出数值而决定,如果退出状态为0,则相当与判断正确,执行嵌套在if语句内的命令。通过上面的说明可以看出如果有下面的语句

      if 0 
      then
      	echo "Hello"
      fi
      

      这条语句是错误的,因为“0”并非是一条命令,因此该语句无法执行,但是不论在C还是python中

      if (1)
          cout << "Hello"
      

      这种语句是可以执行的,归结原因是一般语言中if后面判断的是数值,而bash脚本中if判断的是后面命令的退出状态的状态码。

    • if-then-else语句

      理解了if-then语句,那么这条语句就没有什么好解释的了

      if command
      then
      	commands
      else
      	commands
      fi
      
    • 嵌套if语句

      if command1
      then
      	commands
      elif command2
      then
      	commands
      fi
      

  1. test命令

    test命令的作用就是将想要进行判断的东西转换成一个command以供作为if后面的判断命令,通常用法如下

    if test -n str1
    then
    	echo "Hello"
    fi
    

    该代码块用于判断str1是否为空字符,若是,则test -n str1的退出状态码为0,否则为1。现在对于我来说test很少使用,但是需要知道,为了代码美观便于阅读,常常用方括号来代替test命令

    if [ -n str1 ] 
    then
    	echo "Hello"
    fi
    

    需要注意的是想对应的写法**[空格command空格]**,形象地说命令和方括号之间不能够挨着,否则语法错误。这种方括号的写法使用时间长了容易造成误解,C语言中括号往往代表这整体,但是这里的方括号代表这一个语句,你可以在你的bash中单独输入[ -n "Hello" ]或者[ -n "" ],绝对不会报错,因为行面两个就是标准的命令,接下来可以使用echo $?,来查看代码的推出状态。

    下面是bash中一些常用比较命令的条件参数,需要使用的时候可以查询

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U7UyJKzc-1614520145296)(/home/einstein/.config/Typora/typora-user-images/image-20210222123239336.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bAtjwWAy-1614520145298)(/home/einstein/.config/Typora/typora-user-images/image-20210222123300597.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1m5CGx7-1614520145301)(/home/einstein/.config/Typora/typora-user-images/image-20210222123325797.png)]


  1. 复合条件测试

    表达式

    [ condition1 ] && [ condition2 ]
    [ condition1 ] || [ condition2 ]
    

    前者是与,后者是或。


  2. if-then的高级特性

    if后面的判断之前我们一直使用[ expression ],更本质的说应该是使用test expression,来进行判断。高级特性中会使用((expression))[[expression]],两种类型。

    双圆括号((expression)),扩展了数学运算,加入了自加,自减,乘方等高级的数学运算

    if ((${var} ** 2 < 90))
    then
    	commands
    fi
    

    上面代表如果变量var的二次方小于90,则执行if内的命令,否则跳过。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0xJS3MjZ-1614520145304)(/home/einstein/.config/Typora/typora-user-images/image-20210223170855471.png)]

    这是更多的双圆括号的扩展运算,需要自查。

    双方括号提供模式匹配,不过我一般都使用正则表达,几乎没有使用过双方括号,需要可以自己查阅书籍257页。


  3. case命令

    case命令应该是类似与C语言中的switch命令,用于解决多个if ... elif ... elif这种情况,但是不论是C还是bash中我基本没有用过,我认为知道即可

    case var in 
    parttern1 | parttern2) commands1;;
    parttern3) commands2;;
    *) default commands3;;
    esac
    

    上面的语句的意思是当变量var与模式parttern1或者模式parttern2匹配时候,执行commands1,当与parttern3匹配时候执行commands2,其他情况执行commands3


十三章 更多结构化命令

  1. for命令

    • 如果会C或python,对于for命令应该相当熟悉

      for i in list
      do
      	commands
      done
      

      不看do和done的话这个命令和python中的for用法很相似。

      for命令中的重点就在于命令中的list,在python中存在数组数据类型list。虽然bash中也存在数组,但是和python中的数组并不完全相同,后续会有相关内容,不过这里的list不是bash中的数组,而是一串使用空格作为分割的字符

      for i in one two three four five
      do
      	echo ${i}
      done
      输出:
      one
      two
      three
      four
      five
      

      反正我刚刚学到这里的时候感觉这里处理的好简陋,不过bash就是这样处理list的,list中两个量之间使用空格作为分隔,并且看起来这个list没有所谓的边界,给人很不习惯的感觉。python和C中list类型的数据都使用方括号作为边界进行分割的。

      另外如果我想要的一个元素中间带有空格,我们需要使用双引号来告诉计算机这是一个整体

      for i in one "two three" four five
      do
      	echo ${i}
      done
      输出:
      one
      two three
      four
      five
      

      记住双引号可以告诉计算机那些是一个整体。关于双引号的问题,其实在if篇章那里我就曾经遇到过,我记得当时发了一个相关问题的帖子在贴吧里面

      https://tieba.baidu.com/p/6602217756

      这里面我觉得5楼还是给了我一些提示,当变量中没有空格的时候$var"$var"无差别,当存在空格的时候差别就会显现出来,不过具体有什么差别我现在还弄不清本质的原因,不过如果你想对一个变量var进行操作,不论是判断还是其他的,如果不嫌弃麻烦"${var}",这样不论变量中是有空格还是有回车,准没错。所以我后来在写脚本的时候为了避免不必要的报错或者命令的错误运行,我都是写成做后一种。

      此外,实际中我们较为常用的是将命令的结果作为list进行循环

      for i in `seq 10`
      do
      	echo $i
      done
      

      这里的seq 10是bash中的命令,自动生成从1到10的数字,在本命令块中相当于从1到10进行输出,当然也可当作计数器来指定循环次数。例如

      for i in `seq 100`
      do 
      	echo $RANDOM % 7 | bc
      done
      

      这里的作用是输出100个0到6的随机数字,至于这些随机数字用来干什么,不论是抽签还是模特卡罗计算都是随你心意。


    • 更改字段分隔符

      bash中默认情况下是按照

      • 空格
      • 制表符
      • 换行符

      这几个符号来将字段进行分割的,如果我想要一句一句地输出一个英文诗歌,没一句诗词为一行,但是每一句中又有多个相互由空格分割的单词构成,按照默认的方式输出便会输出每一个单词。因此,我们可以将分隔符i变量进行更改

      IFS=$'\n'
      

      这样分隔符变量IFS就只认换行符。或是可以自行添加其他符号作为分隔符内

      IFS=$'\n':;"
      

      上面这句就是将换行,冒号,分号,双引号作为分隔符。

      另外需要注意有认为有以下几点

      • 首先更改IFS变量的语法有些奇怪,比如将换行赋予IFS变量IFS=$'\n'是这个样子的

      • 其次如果我们更改了IFS变量如何查看?如果按照普通查看系统变量的方法echo,你应该什么也看不到,因为换行,空格,还是制表符,你看到的都是nothing。在网上来看一般查看IFS变量内容的方法是查看其16进制内容echo $IFS | hexdump -C,或者echo $IFS | hexdump,即使这样也不是很直观。

      • 从以上两点中可以看出,如果非必要最好不要更改次变量,不方便改,也不方便查看。当然如果是不改不行的情况下,一定要记住不要忘记更改回来,因为这个是系统变量,好多脚本都是用到这个变量的,如果不改回来会出现奇怪的错误。书中建议。

        IFS.OLD=$IFS
        IFS=$'\n'
        <在代码中使用新的IFS值>
        IFS=$IFS.OLD
        
    • 用通配符读取目录内容

      在一般情况下,如果想要读取该目录下的内容,我习惯

      for i in `ls`
      do
      	echo $i
      done
      

      但是这里的命令仅仅适用于目录下的文件名称均没有空格的情况,所以,更加一般的读取目录的方法便是使用通配符来读取

      for i in $HOME/*
      do
      	echo $i
      done
      

  1. C语言风格的for命令

    之前的是pyhon风格的for命令,而bash中也有C风格的for命令,不过需要使用到双圆括号((expression)),在之前if章节中我们学到双圆括号能够扩展数学运算,包括乘方,自加,自减等运算,C风格的for命令中主要就包含自加或者自减运算。

    for ((a=1;a<10;a++))
    do
    	echo $a 
    done  
    

    当然,不论是python风格的还是C风格的都能够达到同样的效果。

  2. while命令

    while test command
    do
      	other commands
      done
    

    test command结果是0,继续循环,否则退出循环。这其中的test可以换成中括号,或是双括号,甚至命令,比如正则表达。因该在使用正则表达时候ls -l | grep zip,当匹配到相应内容,可以尝试使用echo $?来查看退出状态码是0,正好可以进入循环对相应的文件进行操作。


  3. until命令

    until test commands
    do
    	other commands
    done
    

    until命令是当test commands的退出状态为1时进行循环,直到其变为0,则停止循环。

    至于使用for,还是while,还是until。全凭借自己写代码的思路顺畅程度,总的来说三种循环之间是可以相互转化的。我比较习惯使用for或者while,until命令很少使用。


  4. 控制循环

    控制循环主要是两个命令breakcontinue

    如果对于C熟悉的话对于break一定不陌生,break可以从循环中跳出来,不过bash中break可以跳出多层循环,例如break 2便可以跳出层循环

    for i in list1
    do
    	for j in list2
    	do
    		if command
    		then
    			break 2
    		fi
    	done
    done
    

    上面的代码块便可以跳到for循环的最外面。

    continue命令用于跳过其下面的语句直接执行下一次循环,对于C熟悉的话不需要我多解释。


十四章 处理用户输入

  1. 命令行参数与特殊参数变量

    • 读取脚本以及脚本名称

      该脚本名称为hello.sh

      #/bin/bash
      echo $0
      echo $1
      echo $2
      echo $3
      

      执行上面新建的脚本如下

      ./hello.sh you are a chinese
      输出:
      ./hello.sh
      you
      are
      a
      

      从上面代码执行情况可以看出${数字}是一种特殊的变量,当然数字从必须大于等于0。改变量可以从脚本外面读取参数,注意当数字大于9的时候必须要加上大括号,而数字为个位数字的时候可加可不加。一般来说$0变量显示的是脚本的名还带有路径,这样看起来并不是很好看,特别是当带有绝对路径的时候长长的一大串并不适合进行阅读,因此有一个basename的工具可以去掉文件名中的路径。

      #/bin/bash
      echo `basename $0`
      echo $1
      echo $2
      echo $3
      

      执行命令

      ./hello.sh you are a chinese
      输出:
      hello.sh
      you
      are
      a
      

      这个basename命令一般我是在使用通配符遍历文件过程中使用,可以有效去除文件名称中的路径。

    • 参数统计以及抓取所有参数

      特殊变量$#,在bash脚本中其含义是所有外界参数的个数,像是上面所执行的命令./hello.sh you are a chinese,如果在该脚本中加入$#变量,该变量的数值就会变成4,注意$#变量中不包含脚本名称,或是说不包含$0,因此,我们就可以在脚本开头添加if [ $# -eq n ]这种语句来判断是否参数个数正确。因为如果脚本中使用到参数$n,而恰巧使用脚本过程中添加的参数个数没有达到n,则脚本会出现错误。

      #/bin/bash
      echo `basename $0`
      echo $1
      echo $2
      echo $3
      echo $#
      

      执行

      ./hello.sh you are a chinese
      输出:
      hello.sh
      you
      are
      a
      4
      

      另外还有两个特殊的变量分别是$*$@

      $*将所有参数当作一个整体来进行保存

      $@将所有参数当作一个list来进行保存

      还是从前面的命令来看./hello.sh you are a chinese。对于这个脚本,$*变量的内容是"you are a chinese"作为对比,$@变量所代表的内容是you are a chinese。区别就在是否有双引号,或是说是否看作整体。前者用for循环不出单词的,但是后者可以。

    • 总结

      到目前为止,特殊变量基本全部论述完成,所以在这里总结一些

      $?				#上一条命令的退出状态码
      ${数字}			#脚本的参数变量
      $#				#脚本的参数总个数
      $*				#脚本的所有参数(作为整体)
      $@				#脚本的左右参数(作为list)
      

  2. 获取用户输入内容

    相当于C中的scanf命令,C++中的cin >>命令,python中的input命令,bash中使用read命令来读取用户输入内容。最基本的用法如下

    read 变量名
    

    将用户输入的内容付给变量,需要注意的是如果不给出需要赋予的变量的名称,则系统默认将read来的变量赋予给系统变量REPLY

    更多的用法可以执行read --help来进行查看

    read: read [-ers] [-a 数组] [-d 分隔符] [-i 缓冲区文字] [-n 读取字符数] [-N 读取字符数] [-p 提示符] [-t 超时] [-u 文件描述符] [名称 ...]
        从标准输入读取一行并将其分为不同的域。
        
        从标准输入读取单独的一行,或者如果使用了 -u 选项,从文件描述符 FD 中读取。
        该行被分割成域,如同词语分割一样,并且第一个词被赋值给第一个 NAME 变量,第二
        个词被赋值给第二个 NAME 变量,如此继续,直到剩下所有的词被赋值给最后一个 NAME
        变量。只有 $IFS 变量中的字符被认作是词语分隔符。
        
        如果没有提供 NAME 变量,则读取的行被存放在 REPLY 变量中。
        
        选项:
          -a array	将词语赋值给 ARRAY 数组变量的序列下标成员,从零开始
          -d delim	持续读取直到读入 DELIM 变量中的第一个字符,而不是换行符
          -e	使用 Readline 获取行
          -i text	使用 TEXT 文本作为 Readline 的初始文字
          -n nchars	读取 nchars 个字符之后返回,而不是等到读取换行符。
        		但是分隔符仍然有效,如果遇到分隔符之前读取了不足 nchars 个字符。
          -N nchars	在准确读取了 nchars 个字符之后返回,除非遇到文件结束符或者读超时,
        		任何的分隔符都被忽略
          -p prompt	在尝试读取之前输出 PROMPT 提示符并且不带
        		换行符
          -r	不允许反斜杠转义任何字符
          -s	不回显终端的任何输入
          -t timeout	如果在 TIMEOUT 秒内没有读取一个完整的行则超时并且返回失败。
        		TMOUT 变量的值是默认的超时时间。TIMEOUT 可以是小数。
        		如果 TIMEOUT 是 0,那么仅当在指定的文件描述符上输入有效的时候,
        		read 才返回成功;否则它将立刻返回而不尝试读取任何数据。
        		如果超过了超时时间,则返回状态码大于 128
          -u fd	从文件描述符 FD 中读取,而不是标准输入
        
        退出状态:
        返回码为零,除非遇到了文件结束符、读超时(且返回码不大于128)、
        出现了变量赋值错误或者无效的文件描述符作为参数传递给了 -u 选项。
    

    其中较为常用的是-p-t参数。

    更多关于参数的高级用法我不常用,因此本章没有说明,详细的可以阅读书籍


十五章 呈现数据

  1. 理解输出和输入

    文件描述符缩写描述
    0STDIN标准输入
    1STDOUT标准输出
    2STDERR标准错误

    首先我们可以来直观地理解上述这些输入和输出。对于标准输入,可以简单地理解为计算机从外界获取到的输入信息,至于我们的电脑,那基本就是键盘和鼠标对电脑输入的信息,标准输入我们可以<来进行重定向。所谓重定向,就是更改原本默认的信息流向,比如标准输入正常情况下我们只能从键盘或是鼠标中进行输入,但是通过重定向,我们可以将文件中的内容作为输入内容。

    同样的标准输出,也是同理的,正常情况下标准输出的内容会显示在显示器上以供人类进行查看,但是我们也可以使用>将标准输出重定向到一个文件中。

    ls -l > text
    wc -l < text
    

    上面的命令就是将该目录下的文件重定向到一个text文本中,然后利用输入重定向将text文本中的内容输入到wc -l,作为其参数,计算一共有多少行文本,在这里其意义就是有多少个文件。看到这里其实可以想到之前的一个命令。

    ls -l | wc -l
    

    从某种角度上来讲,管道命令也是一种特殊的重定向。

    最后一种就是标准错误,比如说输入一个错误命令然后利用>符号进行重定向到一个文本中,你会发现依旧有一条错误信息显示在屏幕上,并且打开刚才定向的文本,会发现里面是nothing。这是因为这种错误提示信息是标准错误,而> 符号是用于重定向标准输出的,如果想要重定向标准错误因该使用下面的命令。

    nihao 2> text
    

    这样打开text文本会发现里面有bash: nihao:未找到命令这样的文字。

    由于标准错误的重定向符号不是>而是2>,因此很自然地可以想到,标准输入和标准输出的重定向符号是简化的,完整的标准输入和输出的重定向符号应该是0<1>。这个在后面的创建重定向的时候非常有用。

    重定向的好处在于可以把系统显示在屏幕上面的东西保存在一个文件中从而可以随时查看以及翻阅,比如在我编译一个程序的时候,假设时长在40分钟,我不可能,也不愿意一直盯着屏幕来看,因此我就可以这样做

    编译程序命令 > stdout 2> stderr
    

    这样在这个编译程序的过程中我就可以去做其他事情,并且没有一丝丝的担心,因为如果编译成功,当然是好事,但是如果编译错误,我也可以查看两个文本stdoutstderr 来仔细查看到底哪里出了问题。当然实际上我会使用

    编译程序命令 | tee file 1>&2
    

    相关后续会进行解释


  2. 脚本中的重定向输出

    前面主要介绍了什么是输入和输出,以及简单地介绍了一下一般常用的重定向,将标准输入输出定向到文本文件中。这里将要讲述更一般的重定向。

    文件描述符号0,1,2,这三个已经是系统默认的文件描述符号,描述相应的标准错误,标准输出以及标准输入。自然会想到其他数字呢,其他数字是空闲的,这样我们就可以自己创建自己的重定向,在创建重定向之前需要了解临时重定向以及永久重定向,临时重定向就是该重定向指令之负责想对应的语句,比如下面的脚本

    #!/bin/bash
    echo "Hello" > &2
    echo "World"
    

    这里要简要说明一下echo "Hello" > &2这句,我们正常的重定向到文件是echo "Hello" > file这样的。但是这句中并不是将作为标准输出的Hello单词重定向到普通文件,而是将其重定向到特殊的文件描述附——标准错误2,在重定向中想要定向的文件描述符需要在相应的数字前面加上&,这不禁让我想到C语言中的地址描述,从某种角度来讲这本质上也是一种地址。在执行这个脚本后表面上看不出什么,但是如果将输出内容重定向一下

    ./Hello.sh > stdout 2> stderr
    

    就会发现stdout文件中只有world单词,而stderr中只有Hello单词。

    以上便是临时重定向,重定向的语句只负责相应的句子。而永久冲定向则顾名思义,其重定向的作用是永久的,当然这个永久的对象是想对于脚本来说的,当脚本完成退出后,该重定向不再起作用

    #!/bin/bash
    exec 1 > &2
    echo "Hello" 
    echo "World"
    

    执行./Hello.sh > stdout 2> stderr后会发现stdout文本中无内容,stderr中存在Hello和World两个单词。这边是永久重定向

    还有一个例子就是重定向输入的

    $ cat test12
    #!/bin/bash
    # redirecting file input
    exec 0< testfile
    count=1
    while read line
    do
    echo "Line #$count: $line"
    count=$[ $count + 1 ]
    done
    $ ./test12
    Line #1: This is the first line.
    Line #2: This is the second line.
    Line #3: This is the third line.
    $
    

    这个书中的例子让我联想到C和python中的文件读取f.open()f.read(),f.close()等过程,可以说bash脚本中的重定向对标这一般编程语言的文件读取,写入过程。


  3. 创建自己的重定向

    这里的内容我较少使用,主要因为3个系统重定向文件对于我来说基本足够了,但是这里还是要说一下加深对于重定向的理解。

    exec 3>file
    echo "Hello" > &3
    

    这个便是创建一个自定义的重定向3,并且将其定向到一个文件file中,当我执行脚本过程中便有三个类型的数据流在输出,标准输出,将数据输出到屏幕或者其他位置。标准错误,将数据输出到屏幕或者其他位置。自定义输出3,将数据输出到文件file。

    最后由于exce 3>file在整个脚本中都其作用,如果在脚本一半之后想要关闭该文件描述,则可以执行下面的语句

    exce 3>&-
    

  4. 总结

    最后通过上面的论述对于重定向也有了一定的理解,最后想说明解释一下重定向在我日常中使用最广泛的情况,当我需要将一段指令的输出(标准输出以及标准错误)定向到一个文本中的时候,我一般会使用下面的命令

    command > file 2>&1
    

    首先命令的标准输出定向到file文件中,也就是说只要是标准输出,都会在file文件中记录,那么标准错误怎么办呢?之前我们曾经将标准错误和标准输出分到两个文件中进行记录

    command > file 2> error
    

    从上面的代码可以看到当指令输出标准错误后,数据流会向后面流动,直到数据流遇到一个可以安排自己位置地方,如果到指令末尾也没有安家之处,则标准错误会默认输出到显示屏幕上面。

    command > file 2>&1在这一条指令中,标准错误数据流会沿着指令向后走动,直到遇到了2>&1这条指令,标准错误才知道,哦原来我是被安排到标准输出去了,因此标准错误数据流便摇身一变,重定向为标准输出。而标准输出又该到哪里呢?标准输出能够输出到显示屏幕中吗?当然不能,因为在前面已经指定了标准输出的数据流需要重定向到file文件中,因此原本的标准错误数据又流回到了file文件中。经过上面的这一圈下来,标准输出和标准错误的数据都进到了file文件中,并且顺序完全一样。


十六章 控制脚本

这里我常用的内容主要是作业控制,并且这些内容和脚本的关系并不是很密切,或是说linux下的几乎所有程序都可以利用这章节的相关内容来进行控制。另外本章的内容需要程序运行一段时间才能看到效果,因此编写了下面的脚本以供测试

#!/bin/bash
for i in `seq 10`
do
    sleep 1
    echo $i					#输出到终端
    #echo $i >> sleep       #输出到文本
done
  1. 处理信号

    关于linux的处理信号,如果长期使用linux的用户我相信并不陌生,比如在使用tar或者7z命令对一个文件进行压缩或是解压,当由于某些原因需要暂停相应的进程,记住是暂停不是停止。可以使用组合按键Ctrl-Z。当想要继续进行未完成的压缩文件的进程时可以使用fg命令来继续进行。

    常用的关于信号处理如下

    信号描述
    1SIGHUP挂起进程
    2SIGINT终止进程
    3SIGQUIT停止进程
    9SIGKILL无条件终止进程
    15SIGTERM尽可能终止进程
    17SIGSTOP无条件停止进程,但不是终止进程
    18SIGTSTP停止或暂停进程,但不终止进程
    19SIGCONT继续运行停止的进程

    因此,我们在使用linux中常用的比如Ctrl-Z是信号1,当想要强制停止某一个程序的时候Ctrl-C是信号2。再比如当某一个程序卡住。或者程序崩溃的时候我们可以使用kill -9 PID,来结束进程,也就是上面的SIGKILL信号。


  2. 后台模式运行脚本

    后台运行脚本或者程序,可以在程序或者脚本的后面加上&,比如

    sleep 10 &
    

    我们可以通过ps命令或者jobs命令来查看在后台运行的命令状态,是运行中,还是已完成。

    另外需要说明的一点是在后台运行过程中,如果我们将终端关闭,则该终端下的后台运行程序也会关闭,有另一种后台运行方法可以消除终端和相应的后台程序的关联,及可以任意关闭终端,但是相应的后台运行程序依旧运行

    nohub ./test.sh &
    

    由于该命令将终端与后台运行程序的关联切断,因此作为程序test.sh的标准输出内容就无法打印到显示器(终端)上,nohup命令会自动将 STDOUT 和 STDERR 的消息重定向到一个名为nohup.out的文件中。


  3. 作业控制

    如果我们将一个进程挂到后台,可以使用jobs或者jobs -l命令进行查看。

    [1]- 1950 Running			 ./test10.sh > test10a.out &
    [2]+ 1952 Running			 ./test10.sh > test10b.out &
    

    其中的1和2可以看作是一种编号,但我们想要重启一个已经暂停的作业时,我们便可以使用这个作业编号来指定相应的进程,其中常用的重启作业的命令有fgbg。简单来说fg是将暂停的作业以前台模式重启,而bg是将暂停的作业以后台模式重启。

    fg 2
    

十七章 创建函数

不论是C还是python中都会有函数这个东西,bash脚本中也不例外,函数在bash脚本中的目的就是减少冗余重复的代码块,将一个功能封装到一个函数中方便调用。

  1. 基本的脚本函数以及函数的返回值

    在bash脚本中基本的函数形式是

    function name {
    	commands
    }
    

    或者

    name() {
    	commands
    }
    

    这两种创建函数的形式是等价的,依照个人习惯进行选择。

    在bash脚本中,创建一个函数相当与在脚本中创建一个子脚本,对于脚本一定有一个退出状态码,运行成功即为0,否测出现意外情况则为其他数值。我们之前说过可以使用echo $?这个特殊变量进行查看。在函数中,这个退出状态码利用return来表示。比如在一个脚本中test.sh

    #!/bin/bash
    name(){
    	commands
    	return 3
    }
    echo $?
    

    上面便是脚本的内容,我们可以执行该脚本./test.sh,会发现该脚本输出的数值为3,也就是脚本中的这个echo $?命令给出的结果。

    这个和C语言中的函数返回数值return有些区别,这里的函数返回的是退出状态码,而C中的return是将函数的结果输出到函数外面。bash中的return限制比较多,return返回数值只能是0~255的整数,当然退出状态码的取值范围就是这个。所以一般不使用return来传递出函数内计算的结果,一般使用下面的方法

    #!/bin/bash
    name(){
    	commands
    	echo "result"
    	return 0
    }
    something=`name`
    

    像是这样,就能够把函数的计算结果赋给something这个变量。所以如果想要传递出函数的计算结果,一般使用echo


  2. 函数中的变量

    在C语言中如果想要向函数中输入变量,一般如下

    int fun(int x)
    {
        int y;
        y=x;
        return y;
    }
    

    而在bash脚本中有所不同,由于脚本中的函数可以看作一个子脚本,因此自然可以想到在向脚本中传入参数时候我们使用$1,$2等特殊参数。

    在此,我们可以利用bash脚本写出和上述C形式函数的等价函数

    #!/bin/bash
    fun (){
    	result=$1
    	echo ${result}
    }
    echo `fun 12`
    

    从上面可以看出脚本中函数如果想要使用**函数外部(脚本内部)**给的参数,需要借用$数字,这个特殊参数来传递变量。

    提到函数就不得不提到函数变量的作用域,这个在这里不详细说明,如果熟悉C或者C++的话一定相当熟悉,在这里只是简单说明一下在函数中定义局部变量使用local value=3这种形式,关键字是local

    #!/bin/bash
    value=5
    fun (){
    	local value=3
    	echo ${value}
    }
    echo `fun`
    echo ${value}
    

  3. 数组变量和函数

    最后要说一下bash语言中的最后一种变量(或是第二种,我个人理解依旧是字符串类型变量)数组变量。数组变量的形式是由圆括号括起来的之间用空格想分隔的一串字符串,形式如下

    m=("hello" 2 "你好" 226 "my car")
    

    由于是字符串数据类型,当然和list的数据类型有所区别,不可以直接for in来进行循环。需要使用序号来引用

    echo ${m[0]}
    echo ${m[1]}
    echo $m[1]
    echo ${m[4]}
    echo $m
    echo ${m[*]}
    输出:
    hello
    你好
    hello[1]
    my car
    hello
    hello 2 你好 226 my car
    

    从上面可以看出以下几点

    • 大括号{}是必须存在的,因为数组序号使用了中括号,如果不加上大括号将变量名的范围扩起来会产生歧义,认为中括号只是一个后加上去的字符串而已,这样中括号就起不到指示数组变量位置的作用
    • 数组的序号是从0开始,这和其他语言都相同
    • 想要输出整个数组echo $m这种形式是不行的,这种形式只能输出数组的地一个变量,想要输出整个数组需要这样echo ${m[*]}其中*可以看作是起到了通配符的作用。

  4. 总结

    创建函数应该是我写的bash脚本中的最后一章了,不能说其他的没有用,只是对于我日常处理文件来说用不到而已。像是关于使用bash脚本写“图形UI”的章节我认为也很有趣,当然这里的UI需要打引号是因为bash写出的UI和印象中的UI不太一样,就有蓝色的提示框,并且提示框后面还有阴影,不过这种UI还是无法与鼠标交互。我写的一些处理文件的脚本一般来说只有我自己使用,当然UI是用不到的了。

    再有就是一些更加高级的,我现在水平还不够写出总结的一些东西,比如正则表达,这个我还是较为常用的,正则表达较为简单,不过学的快,忘记的也快。还有就是sed和gawk,这两个我现在依旧是需要的时候现用现查,这些如果有需要可以好好学习一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值