我自己接触Linux主要是大学学习的Turbolinux --> 根据《鸟哥的Linux私房菜:基础篇》(第三版) --> 马哥的就业班课程。给我的感觉是这些课程对于bash的讲解,理论上是不够的,但是限于时间、篇幅和精力,确实无法讲解的足够深入。在接触了骏马金龙的博客以及bash官方站点后,就会理解骏马兄说的“平常我们学的只是bash的形,而不是bash的神”。最近在写这个系列的学习笔记,经常查阅bash官方手册,真的是有种醍醐灌顶的感觉,但是限于能力和进度问题,有些问题暂且无法做到深入理解,只能给出治标的方法。

学习test命令以及后续的选择语句if和case,需要注意的手册有这些:

组合命令之条件结构体:[[ ... ]]

字符串匹配时使用到的模式匹配

bash特性之条件表达式

Bourne Shell的内置命令test及其常用格式[ .. ]

字符串匹配时,使用扩展的glob或者忽略大小写匹配,需要了解的shell选项:extglob,nocaseglob,nocasematch等。

test简介

测试命令test用于形成一个表达式,结合条件判断语句if-else来判断。

例如可以判断某个文件是否存在,是否具备什么样的特性(可读吗?可写吗?可执行吗?块文件吗?)等等。

测试命令test有三种语法格式:

  1. test EXPRESSION
  2. [ EXPRESSION ]
  3. [[ EXPRESSION ]]

前两种是等价的,应该是没有区别的。注意EXPRESSION两边与中括号之间需要有空格。

双中括号与前两者的区别,主要在于表达式中的操作符如果是这种情况的时候。

  1. [[ string1 > string2 ]]
  2. [[ string1 < string2 ]]

字符串之间比较比较大小,其实比的是在词典中字符的先后顺序。[]应该是基于ASCII来比较,而[[]]应该是基于shell当前的地理位置设置来比较(应该与locale相关的环境变量有关吧)。

更多的信息,可能需要大家去直接看bash的手册吧,我稍微看了下,是真的很难以理解,所以暂时放弃了,只找到了这些简单的区别,按照骏马兄的话,咱只能先学习bash的形了,直接跳着看bash手册会有一种被劝退的感觉。

测试表达式的结果为真或者假,真返回0,假返回1。这个返回值不会显示在终端上,而是保存在shell的特殊变量“$?”中(bash原文中提到的是特殊参数(parameter),变量是一个通过名称表示的参数)。

因此我们只需要在每次测试后echo这个特殊变量的值就可以验证了。

  1. # [ EXPRESSION ]
  2. # echo $?

test实战

数值比较

-eq:equal,是否相等。

  1. [root@c7-server ~]# age=
  2. [root@c7-server ~]# [ $age -eq ]
  3. [root@c7-server ~]# echo $?

-ne:not equal,是否不相等。

  1. [root@c7-server ~]# [ $age -ne ]
  2. [root@c7-server ~]# echo $?

-gt:greater than,是否大于。

  1. [root@c7-server ~]# [ $age -gt ]
  2. [root@c7-server ~]# echo $?

-ge:greater equal,是否大于等于。

  1. [root@c7-server ~]# [ $age -ge ]
  2. [root@c7-server ~]# echo $?

-lt:less than,是否小于。

  1. [root@c7-server ~]# [ $age -lt ]
  2. [root@c7-server ~]# echo $?

-le:less equal,是否小于等于。

  1. [root@c7-server ~]# [ $age -le ]
  2. [root@c7-server ~]# echo $?

字符串比较

==:是否等于。字符串等值比较,使用一个=符号也是可以。

  1. [root@c7-server ~]# name=alongdidi
    [root@c7-server ~]# [ $name == alongdidi ]
  2. [root@c7-server ~]# echo $?

!=:是否不等于。

  1. [root@c7-server ~]# [ $name != alongdidi ]
  2. [root@c7-server ~]# echo $?

=~:基于regex3的扩展正则匹配,并且使用这个操作符的时候必须使用[[]]。

  1. [root@c7-server ~]# [[ $name =~ along(di){} ]]
  2. [root@c7-server ~]# echo $?
  3.  
  4. [root@c7-server ~]# [ $name =~ along(di){} ]
  5. -bash: syntax error near unexpected token `('
  6. [root@c7-server ~]# [ $name =~ along\(di\){} ]
  7. -bash: [: =~: binary operator expected

-z:判断字符串是否为空。

  1. [root@c7-server ~]# [ -z $name ]
  2. [root@c7-server ~]# echo $?

-n:判断字符串是否不空。

  1. [root@c7-server ~]# [ -n $name ]
  2. [root@c7-server ~]# echo $?

在做字符串的等值比较时,无论是==、!=或者=~,它们都是二元(binary)的运算符,也就是在这个符号的左右两边都必须存在字符串。

如果其中一边为空的话,就会报错。

  1. [root@c7-server ~]# unset name
  2. [root@c7-server ~]# [ $name == tom ]
  3. -bash: [: ==: unary operator expected

因为$name为空,因此整个表达式就变成了。

  1. [ == tom ]

所以bash会报错并告诉你我们期待的是一元(unary)运算符(因为只有tom这个字符串)。一元也可以叫单目,是一样的意思。

解决的办法有三个,对$name使用双引号或者单引号包裹或者将[]换成[[]]。

  1. [root@c7-server ~]# [ "$name" == tom ]
  2. [root@c7-server ~]# echo $?
  3.  
  4. [root@c7-server ~]# [ '$name' == tom ]
  5. [root@c7-server ~]# echo $?
  6.  
  7. [root@c7-server ~]# [[ $name == tom ]]
  8. [root@c7-server ~]# echo $?

文件测试

存在性

-a FILE或者-e FILE:判断文件是否存在。

  1. [root@c7-server ~]# [ -a /etc/passwd ]
  2. [root@c7-server ~]# echo $?
  3.  
  4. [root@c7-server ~]# [ -e /etc/passwd ]
  5. [root@c7-server ~]# echo $?

文件类型

-b FILE:文件是否存在且为块设备文件。

  1. [root@c7-server ~]# ls -l /dev/sda1
  2. brw-rw---- root disk , Jan : /dev/sda1
  3. [root@c7-server ~]# [ -b /dev/sda1 ]
  4. [root@c7-server ~]# echo $?

-c FILE:文件是否存在且为字符设备文件。

  1. [root@c7-server ~]# ls -l /dev/autofs
  2. crw------- root root , Jan : /dev/autofs
  3. [root@c7-server ~]# [ -c /dev/autofs ]
  4. [root@c7-server ~]# echo $?

-d FILE:文件是否存在且为目录文件。

  1. [root@c7-server ~]# ls -ld /root
  2. dr-xr-x---. root root Jan : /root
  3. [root@c7-server ~]# [ -d /root ]
  4. [root@c7-server ~]# echo $?

-f FILE:文件是否存在且为普通文件(即文本文件)。

  1. [root@c7-server ~]# ls -l /etc/passwd
  2. -rw-r--r-- root root Nov : /etc/passwd
  3. [root@c7-server ~]# file /etc/passwd
  4. /etc/passwd: ASCII text
  5. [root@c7-server ~]# [ -f /etc/passwd ]
  6. [root@c7-server ~]# echo $?

-h FILE:文件是否存在且为字符链接文件,即软连接、符号链接。

-L FILE:同上。

  1. [root@c7-server ~]# ls -l /etc/rc.local
  2. lrwxrwxrwx. root root Oct : /etc/rc.local -> rc.d/rc.local
  3. [root@c7-server ~]# [ -h /etc/rc.local ]
  4. [root@c7-server ~]# echo $?
  5.  
  6. [root@c7-server ~]# [ -L /etc/rc.local ]
  7. [root@c7-server ~]# echo $?

-p FILE:文件是否存在且为命名管道文件。

  1. [root@c7-server ~]# ls -l /run/dmeventd-client
  2. prw------- root root Jan : /run/dmeventd-client
  3. [root@c7-server ~]# [ -p /run/dmeventd-client ]
  4. [root@c7-server ~]# echo $?

-S FILE:文件是否存在且为套接字文件。

  1. [root@c7-server ~]# ls -l /run/systemd/shutdownd
  2. srw------- root root Jan : /run/systemd/shutdownd
  3. [root@c7-server ~]# file /run/systemd/shutdownd
  4. /run/systemd/shutdownd: socket
  5. [root@c7-server ~]# [ -S /run/systemd/shutdownd ]
  6. [root@c7-server ~]# echo $?

文件权限

-r FILE:文件是否存在且对当前用户可读。

-w FILE:文件是否存在且对当前用户可写。

-x FILE:文件是否存在且对当前用户可执行。

  1. [root@c7-server ~]# ls -l /etc/passwd
  2. -rw-r--r-- root root Nov : /etc/passwd
  3. [root@c7-server ~]# [ -r /etc/passwd ]
  4. [root@c7-server ~]# echo $?
  5.  
  6. [root@c7-server ~]# [ -w /etc/passwd ]
  7. [root@c7-server ~]# echo $?
  8.  
  9. [root@c7-server ~]# [ -x /etc/passwd ]
  10. [root@c7-server ~]# echo $?

特殊文件权限

-u FILE:文件是否存在且具有SUID权限。

  1. [root@c7-server ~]# ls -l /usr/bin/passwd
  2. -rwsr-xr-x. root root Jun /usr/bin/passwd
  3. [root@c7-server ~]# [ -u /usr/bin/passwd ]
  4. [root@c7-server ~]# echo $?

-g FILE:文件是否存在且具有SGID权限。

  1. [root@c7-server ~]# ls -l /usr/bin/wall
  2. -r-xr-sr-x. root tty Jun /usr/bin/wall
  3. [root@c7-server ~]# [ -g /usr/bin/wall ]
  4. [root@c7-server ~]# echo $?

-k FILE:文件是否存在且具有STICKY权限。

  1. [root@c7-server ~]# ls -ld /tmp/
  2. drwxrwxrwt. root root Jan : /tmp/
  3. [root@c7-server ~]# [ -k /tmp/ ]
  4. [root@c7-server ~]# echo $?

文件是否有内容测试

-s FILE:文件是否存在且有内容。

  1. [root@c7-server ~]# ls -l test.txt
  2. ls: cannot access test.txt: No such file or directory
  3. [root@c7-server ~]# touch test.txt
  4. [root@c7-server ~]# [ -s test.txt ]
  5. [root@c7-server ~]# echo $?
  6.  
  7. [root@c7-server ~]# [ -s /etc/passwd ]
  8. [root@c7-server ~]# echo $?

时间戳测试

-N FILE:文件自身从上一次读操作后是否被修改过。

  1. [root@c7-server ~]# cat test.txt
  2. [root@c7-server ~]# [ -N test.txt ]
  3. [root@c7-server ~]# echo $?
  4.  
  5. [root@c7-server ~]# echo "alongdidi" > test.txt
  6. [root@c7-server ~]# [ -N test.txt ]
  7. [root@c7-server ~]# echo $?

从属关系测试

-O FILE:当前用户是否为文件的属主。

-G FILE:当前用户是否属于文件的属组。

  1. [root@c7-server ~]# ls -ld /home/zwl/
  2. drwx------. zwl zwl Apr /home/zwl/
  3. [root@c7-server ~]# [ -O /home/zwl/ ]
  4. [root@c7-server ~]# echo $?
  5.  
  6. [root@c7-server ~]# [ -G /home/zwl/ ]
  7. [root@c7-server ~]# echo $?

双目测试

FILE1 -ef FILE2:FILE1和FILE2是否指向同一个文件系统的相同inode的硬链接。

  1. [root@c7-server ~]# ln test.txt test.hard
  2. [root@c7-server ~]# ls -li test.txt test.hard
  3. -rw-r--r-- root root Jan : test.hard
  4. -rw-r--r-- root root Jan : test.txt
  5. [root@c7-server ~]# [ test.txt -ef test.hard ]
  6. [root@c7-server ~]# echo $?

FILE1 -nt FILE2:FILE1是否新于FILE2,根据文件的mtime。

FILE1 -ot FILE2:FILE1是否旧于FILE2,根据文件的mtime。

  1. [root@c7-server ~]# [ test.txt -nt /etc/passwd ]
  2. [root@c7-server ~]# echo $?
  3.  
  4. [root@c7-server ~]# [ test.txt -ot /etc/passwd ]
  5. [root@c7-server ~]# echo $?

组合测试条件

即与或非。

[ EXP1 -a EXP2 ]:EXP1和EXP2必须都为true,结果才为true。

[ EXP1 -o EXP2 ]:只要EXP1和EXP2当中有一个为true,结果就为true。

[ ! EXP ]:当EXP为true的时候,结果为false;当EXP为false的时候,结果为true。

练习

如果主机名为空或者包含local字符串,则将主机名设置为www.alongdidi.com。

  1. hostName=$(hostname)
  2. [ -z "${hostName}" -o ${hostName}=~"local" ] && hostname www.magedu.com

注:实际我在CentOS 7,Bash 4.2.46场景下执行该命令,得到的结果不太对。主要问题出在“=~”的判断上。暂时未知如何解决,这里大概知道下思路即可。

命令/脚本状态返回值

上文中我们介绍了特殊变量$?,它存储了测试表达式的测试结果。true=0,false=1。

命令执行的结果也会有这么一个返回值(也可以叫退出状态码),一般返回值0表示命令执行成功,返回值非0(多数情况下是1)则表示失败。

PS:这里也需要注意,大多数编程语言使用1来表示成功/true等。还有大家也要注意和命令执行后的标准输出或者标准错误输出区别开。一个表示命令执行成功与否的结果,另一个则是命令执行的输出结果。

这个返回值在我们执行脚本的时候,也会返回。默认脚本执行的返回值使用的是脚本中最后一条命令的返回值。

如果脚本中前几条命令的执行均成功了,但是最后一条执行失败了,那么整个脚本的$?也是非0的。

我们可以通过exit命令来手工配置退出状态码。bash遇到exit会立即退出当前的shell并将返回值存入父shell的$?变量中。因此可以用来立即退出bash脚本。

  1. [root@c7-server ~]# bash
  2. [root@c7-server ~]# exit
  3. exit
  4. [root@c7-server ~]# echo $?

PS:有的时候退出状态码会异常,可能和返回值的取值有关系。

  1. [root@c7-server ~]# exit
  2. exit
  3. [root@c7-server ~]# echo $?
  4.  
  5. [root@c7-server ~]# bash
  6. [root@c7-server ~]# exit
  7. exit
  8. [root@c7-server ~]# echo $?

自定义返回值一般用于bash脚本中的判断。比如,当某个文件不存在的时候,立即执行exit 5。

脚本会立刻退出,5这个返回值会被返回。一般程序员会事先定义好不同的返回值表达的不同含义,并将其写入文档。

用户根据返回值和该文档来判断脚本为什么中断执行了。

像rsync的man手册中就有定义。

  1. Success
  2. Syntax or usage error
  3. Protocol incompatibility
  4. Errors selecting input/output files, dirs
  5. ...

脚本中的参数

在学习C语言的时候,我们可以向函数执行传递参数的操作。bash脚本编程也是可以的。

可以对脚本进行传参,也可以对函数。

在引用参数的时候,具体是引用脚本的参数还是函数的参数则取决于引用的位置。

按照马哥课程的进度,函数还未学习到,因此这里就不说了。(虽然已经有bash编程的基础)等到写bash函数的博文时,会在提及。

向脚本传参和脚本中引用参数十分简单,示例如下。

  1. [root@c7-server ~]# cat test.sh
  2. #!/bin/bash
  3. echo $
  4. echo $
  5. [root@c7-server ~]# bash test.sh along didi
  6. along
  7. didi

在执行脚本时,脚本名称后面的字符串就是参数,多个参数之间以空格分离,根据参数出现在脚本名称后的位置,在脚本中使用$1、$2、$3...来引用,它们也被称作位置参数。

shift not shit

如果我们想要改变位置参数的位置,就需要使用到shift内置命令。

  1. shift [n]

shift的本意是移动,我们可以理解为拿掉位置参数最左边的n个。默认是1个。

  1. [root@c7-server ~]# cat test.sh
  2. #!/bin/bash
  3. echo $*
  4. shift
  5. echo $*
  6. [root@c7-server ~]# bash test.sh a long di di
  7. a long di di
  8. di di

其他特殊变量

$#:获取脚本被传递的参数的个数。

  1. [root@c7-server ~]# cat test.sh
  2. #!/bin/bash
  3. echo $#
  4. [root@c7-server ~]# bash test.sh a l on g did i

$0:获取脚本的名称,这个名称是执行时的名称。执行的方式不同,值也不同。

  1. [root@c7-server ~]# cat test.sh
  2. #!/bin/bash
  3. echo $
  4. [root@c7-server ~]# bash test.sh
  5. test.sh
  6. [root@c7-server ~]# /root/test.sh
  7. /root/test.sh
  8. [root@c7-server ~]# ./test.sh
  9. ./test.sh

只想获取脚本的名称的话,可结合basename命令。

  1. [root@c7-server ~]# basename $(bash test.sh)
  2. test.sh
  3. [root@c7-server ~]# basename $(/root/test.sh)
  4. test.sh
  5. [root@c7-server ~]# basename $(./test.sh)
  6. test.sh

$*:引用所有的参数。在双引号的情况下,所有参数整合作为一个整体。

$@:引用所有的参数。无论是否有双引号,每个参数自身都作为一个整体。

  1. [root@c7-server ~]# cat test.sh
  2. #!/bin/bash
  3. echo $*
  4. echo $@
  5. [root@c7-server ~]# bash test.sh a long di di
  6. a long di di
  7. a long di di

区别的话,可以看这个例子。

  1. [root@c7-server ~]# cat test.sh
  2. #!/bin/bash
  3. for i in $*; do echo $i; done
  4. for i in $@; do echo $i; done
  5. echo "I am cut-off line"
  6. for i in "$*"; do echo $i; done
  7. for i in "$@"; do echo $i; done
  8. [root@c7-server ~]# bash test.sh a long di di
  9. a
  10. long
  11. di
  12. di
  13. a
  14. long
  15. di
  16. di
  17. I am cut-off line
  18. a long di di
  19. a
  20. long
  21. di
  22. di

更深度的解释,查阅官方文档的话,需要对bash的单词分割(word splitting)和IFS变量有理解才行。

Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量的更多相关文章

  1. Bash脚本编程学习笔记06:条件结构体

    简介 在bash脚本编程中,条件结构体使用if语句和case语句两种句式. if语句 单分支if语句 if TEST; then CMD fi TEST:条件判断,多数情况下可使用test命令来实现, ...

  2. Bash脚本编程学习笔记08:函数

    官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在<Bash脚本编程学习笔记06:条件结构体>中最后所说的,我们应该把一些可能反复执 ...

  3. Bash脚本编程学习笔记07:循环结构体

    本篇中涉及到算术运算,使用了$[]这种我未在官方手册中见到的用法,但是确实可用的,在此前的博文<Bash脚本编程学习笔记03:算术运算>中我有说明不要使用,不过自己忘记了.大家还是尽量使用 ...

  4. Bash脚本编程学习笔记05:用户交互与脚本调试

    用户交互 在<学习笔记04>中我们有提到位置参数,位置参数是用来向脚本传递参数的一种方式.还有一种方式,是read命令. [root@c7-server ~]# read name alo ...

  5. bash脚本编程学习笔记(一)

    bash脚本语言,不同于C/C++是一种解释性语言.即在执行前不需要事先转变为可执行的二进制代码,而是每次执行时经解释器解释后执行.bash脚本语言是命令的堆砌,即按照实际需要,结合命令流程机制实现的 ...

  6. bash脚本编程学习笔记(二)

    1.脚本编程之函数 函数是实现结构化编程重要的思想,主要目的是实现代码重用 定义一个函数: function FUNCNAME { command //函数体 }   FUNCNAME(){ //函数 ...

  7. shell脚本编程学习笔记(一)

    一.脚本格式 vim shell.sh #!/bin/bash //声明脚本解释器,这个‘#’号不是注释,其余是注释 #Program: //程序内容说明 #History: //时间和作者 二.sh ...

  8. Linux Shell脚本编程学习笔记和实战

    http://www.1987.name/141.html shell基础 终端打印.算术运算.经常使用变量 Linux下搜索指定文件夹下特定字符串并高亮显示匹配关键词 从键盘或文件里获取标准输入 [ ...

  9. shell脚本编程学习笔记(三)编写邮件报警脚本

    一.shell编写邮件报警脚本 1.POSTFIX邮件服务器准备 a.首先卸载服务器上自带的sendmail rpm -qa sendmail* //查看安装的sendmail rpm -e send ...

随机推荐

  1. django 发布会签到系统web开发

    引言 最近学习了虫师的发布会签到系统demo,结合自己所学django知识,对demo重新塑造了一下.也是为了练练手,巩固知识.现在就分享一下成果~ Django工作流 学习django web开发, ...

  2. DevOps:运维体系建设

    简介 运维体系的建设的目的在于方便运维工作,通过自动化.规范化.流程化的操作方法提高运维效率,打造一个安全.可靠.高效.可追踪.可回溯的运维环境,实现一个高可用.高并发.具备高容错.自我修复.故障能快 ...

  3. MongoDB -> kafka 高性能实时同步(采集)mongodb数据到kafka解决方案

    写这篇博客的目的 让更多的人了解 阿里开源的MongoShake可以很好满足mongodb到kafka高性能高可用实时同步需求(项目地址:https://github.com/alibaba/Mong ...

  4. java虚拟机jvm启动后java代码层面发生了什么?

    java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, ...

  5. 《自拍教程14》Linux的常用命令

    Linux操作系统, 包括我们大家熟知的Android, Ubuntu, Centos, Red Hat, UOS等. 这些常用命令先大概了解下,当然能熟练掌握并运用到实际工作中那最好不过了. 后续技 ...

  6. k8s系列---存储卷pv/pvc。configMap/secert

    因为pod是有生命周期的,pod一重启,里面的数据就没了.所以我们需要数据持久化存储. 在k8s中,存储卷不属于容器,而是属于pod.也就是说同一个pod中的容器可以共享一个存储卷. 存储卷可以是宿主 ...

  7. 动态规划------背包问题(c语言)

    /*背包问题: 背包所能容纳重量为10:共五件商品,商品重量用数组m存储m[5]={2,2,6,5,4}, 每件商品的价值用数组n存储,n[5]={6,3,5,4,6};求背包所能装物品的最大价值. ...

  8. Serverless + Egg.js 后台管理系统实战

    本文将介绍如何基于 Egg.js 和 Serverless 实现一个后台管理系统 作为一名前端开发者,在选择 Nodejs 后端服务框架时,第一时间会想到 Egg.js,不得不说 Egg.js 是一个 ...

  9. while 循环 实例

    /*int i=0; while(i<100){// 循环条件 while先执行后循环 printf("while第%d遍循环体\n",i);//循环体 i++; } */ ...

  10. 来去学习之---KMP算法--next计算过程

    一.概述 KMP算法是一种字符串匹配算法,比如现有字符串 T:ABCDABCDABCDCABCDABCDE, P:ABCDABCDE P字符串对应的next值:[0,0,0,0,1,2,3,4,0] ...