我自己接触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有三种语法格式:

test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]

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

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

[[ string1 > string2 ]]
[[ string1 < string2 ]]

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

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

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

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

# [ EXPRESSION ]
# echo $?

test实战

数值比较

-eq:equal,是否相等。

[root@c7-server ~]# age=
[root@c7-server ~]# [ $age -eq ]
[root@c7-server ~]# echo $?

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

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

-gt:greater than,是否大于。

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

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

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

-lt:less than,是否小于。

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

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

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

字符串比较

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

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

!=:是否不等于。

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

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

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

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

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

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

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

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

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

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

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

[ == tom ]

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

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

[root@c7-server ~]# [ "$name" == tom ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ '$name' == tom ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [[ $name == tom ]]
[root@c7-server ~]# echo $?

文件测试

存在性

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

[root@c7-server ~]# [ -a /etc/passwd ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ -e /etc/passwd ]
[root@c7-server ~]# echo $?

文件类型

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

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

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

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

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

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

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

[root@c7-server ~]# ls -l /etc/passwd
-rw-r--r-- root root Nov : /etc/passwd
[root@c7-server ~]# file /etc/passwd
/etc/passwd: ASCII text
[root@c7-server ~]# [ -f /etc/passwd ]
[root@c7-server ~]# echo $?

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

-L FILE:同上。

[root@c7-server ~]# ls -l /etc/rc.local
lrwxrwxrwx. root root Oct : /etc/rc.local -> rc.d/rc.local
[root@c7-server ~]# [ -h /etc/rc.local ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ -L /etc/rc.local ]
[root@c7-server ~]# echo $?

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

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

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

[root@c7-server ~]# ls -l /run/systemd/shutdownd
srw------- root root Jan : /run/systemd/shutdownd
[root@c7-server ~]# file /run/systemd/shutdownd
/run/systemd/shutdownd: socket
[root@c7-server ~]# [ -S /run/systemd/shutdownd ]
[root@c7-server ~]# echo $?

文件权限

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

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

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

[root@c7-server ~]# ls -l /etc/passwd
-rw-r--r-- root root Nov : /etc/passwd
[root@c7-server ~]# [ -r /etc/passwd ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ -w /etc/passwd ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ -x /etc/passwd ]
[root@c7-server ~]# echo $?

特殊文件权限

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

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

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

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

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

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

文件是否有内容测试

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

[root@c7-server ~]# ls -l test.txt
ls: cannot access test.txt: No such file or directory
[root@c7-server ~]# touch test.txt
[root@c7-server ~]# [ -s test.txt ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ -s /etc/passwd ]
[root@c7-server ~]# echo $?

时间戳测试

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

[root@c7-server ~]# cat test.txt
[root@c7-server ~]# [ -N test.txt ]
[root@c7-server ~]# echo $? [root@c7-server ~]# echo "alongdidi" > test.txt
[root@c7-server ~]# [ -N test.txt ]
[root@c7-server ~]# echo $?

从属关系测试

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

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

[root@c7-server ~]# ls -ld /home/zwl/
drwx------. zwl zwl Apr /home/zwl/
[root@c7-server ~]# [ -O /home/zwl/ ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ -G /home/zwl/ ]
[root@c7-server ~]# echo $?

双目测试

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

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

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

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

[root@c7-server ~]# [ test.txt -nt /etc/passwd ]
[root@c7-server ~]# echo $? [root@c7-server ~]# [ test.txt -ot /etc/passwd ]
[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。

hostName=$(hostname)
[ -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脚本。

[root@c7-server ~]# bash
[root@c7-server ~]# exit
exit
[root@c7-server ~]# echo $?

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

[root@c7-server ~]# exit
exit
[root@c7-server ~]# echo $? [root@c7-server ~]# bash
[root@c7-server ~]# exit
exit
[root@c7-server ~]# echo $?

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

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

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

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

      Success
Syntax or usage error
Protocol incompatibility
Errors selecting input/output files, dirs
...

脚本中的参数

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

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

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

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

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

[root@c7-server ~]# cat test.sh
#!/bin/bash
echo $
echo $
[root@c7-server ~]# bash test.sh along didi
along
didi

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

shift not shit

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

shift [n]

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

[root@c7-server ~]# cat test.sh
#!/bin/bash
echo $*
shift
echo $*
[root@c7-server ~]# bash test.sh a long di di
a long di di
di di

其他特殊变量

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

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

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

[root@c7-server ~]# cat test.sh
#!/bin/bash
echo $
[root@c7-server ~]# bash test.sh
test.sh
[root@c7-server ~]# /root/test.sh
/root/test.sh
[root@c7-server ~]# ./test.sh
./test.sh

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

[root@c7-server ~]# basename $(bash test.sh)
test.sh
[root@c7-server ~]# basename $(/root/test.sh)
test.sh
[root@c7-server ~]# basename $(./test.sh)
test.sh

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

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

[root@c7-server ~]# cat test.sh
#!/bin/bash
echo $*
echo $@
[root@c7-server ~]# bash test.sh a long di di
a long di di
a long di di

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

[root@c7-server ~]# cat test.sh
#!/bin/bash
for i in $*; do echo $i; done
for i in $@; do echo $i; done
echo "I am cut-off line"
for i in "$*"; do echo $i; done
for i in "$@"; do echo $i; done
[root@c7-server ~]# bash test.sh a long di di
a
long
di
di
a
long
di
di
I am cut-off line
a long di di
a
long
di
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. CenOS安装MySQL服务

    说明:本教程是CenOS安装MySQL服务. 0.    预备知识: 什么是rpm RMP 是 LINUX 下的一种软件的可执行程序,你只要安装它就可以了.这种软件安装包通常是一个RPM包(Redha ...

  2. asp.net core 3.x 身份验证-1涉及到的概念

    前言 从本篇开始将围绕asp.net core身份验证写个小系列,希望你看完本系列后,脑子里对asp.net core的身份验证原理有个大致印象.至于身份验证是啥?与授权有啥联系?就不介绍了,太啰嗦. ...

  3. 1. 学习Linux操作系统

    1.熟练使用Linux命令行(鸟哥的Linux私房菜.Linux系统管理技术手册) 2.学会Linux程序设计(UNIX环境高级编程) 3.了解Linux内核机制(深入理解LINUX内核) 4.阅读L ...

  4. Mysql索引优化简单介绍

    一.关于MySQL联合索引 总结记录一下关于在MySQL中使用联合索引的注意事项. 如:索引包含表中每一行的last_name.first_name和dob列,即key(last_name, firs ...

  5. linux入门系列12--磁盘管理之分区、格式化与挂载

    前面系列文章讲解了VI编辑器.常用命令.防火墙及网络服务管理,本篇将讲解磁盘管理相关知识. 本文将会介绍大量的Linux命令,其中有一部分在"linux入门系列5--新手必会的linux命令 ...

  6. Go Module下使用本地包

    介绍两种方式: 方式一(推荐): 严格的说,方式一是使用项目目录下的go文件. 项目目录如下: |── studyModule //项目主目录 | |──log //主目录下文件夹 | | |──lo ...

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

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

  8. jmeter接口测试(登录、注册)

    Jmeter 进行接口测试流程: Jmeter 的下载地址:http://jmeter.apache.org/download_jmeter.cgi 下面举例说明使用流程,有两种参数传递的方式,我们以 ...

  9. FastDFS 配置文件 tracker.conf

    FastDFS 版本5.05 配置文件分为三部分   控制器:tracker.conf存储器:storage.conf 客户端:client.conf 文件位置:/etc/fdfs 基本配置(基础配置 ...

  10. Web登录配置-H3C-S5120-LI系列

    1.开启http服务和修改端口号 ip http enable ip http port  80 2.添加用户和密码.用户名:admin.密码:admin@123 [Sysname] local-us ...