[转帖]Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量

bash,脚本,编程,学习,笔记,测试,命令,test,状态,返回值,位置,参数,特殊,变量 · 浏览次数 : 0

小编点评

**关于主机名设置** 在设置主机名时,可以使用 `hostname` 变量存储主机名,并使用 `&` 符号将变量的值传递给 `bash` 脚本。 例如: ```bash hostname long didialongdidialongdidi ``` **关于特殊变量的设置** 特殊变量,如 `$?`、`$*`、`$`、``等,用于存储脚本的执行结果、输入参数、输出参数等。 例如: ```bash echo $$? echo $$* echo $$ echo $` ``` **关于双引号的使用** 在使用双引号时,所有参数合并作为一个整体。 例如: ```bash echo "$@" ``` **关于`shift` 命令的用法** `shift` 命令用于移动参数,并移动参数到 `$` 变量中。 例如: ```bash shift [n]shift ``` **关于``双引号的使用** ``双引号用于引用变量的名称,并允许在变量中包含 ``字符。 例如: ```bash echo "$@" ``` **关于``双引号的用法** ``双引号用于将多个参数作为一个整体传递给 `bash` 脚本。 例如: ```bash echo "$0" ``` **关于``双引号的使用** ``双引号用于将字符串变量作为一个整体传递给 `bash` 脚本。 例如: ```bash echo "$1" ``` **关于``双引号的使用** ``双引号用于将变量的值作为一个整体传递给 `bash` 脚本。 例如: ```bash echo "$2" ``` **总结** 在使用 `bash` 脚本时,需要理解特殊变量、`shift` 命令和``双引号的使用。通过理解这些概念,可以编写并执行有效的脚本。

正文

https://www.cnblogs.com/alongdidi/p/test_exitStatus_positionalAndSpecialParameter.html

 

我自己接触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=28
[root@c7-server ~]# [ $age -eq 28 ]
[root@c7-server ~]# echo $?
0

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

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

-gt:greater than,是否大于。

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

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

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

-lt:less than,是否小于。

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

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

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

字符串比较

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

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

!=:是否不等于。

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

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

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

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

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

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

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

在做字符串的等值比较时,无论是==、!=或者=~,它们都是二元(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 $?
1
[root@c7-server ~]# [ '$name' == tom ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# [[ $name == tom ]]
[root@c7-server ~]# echo $?
1
复制代码

文件测试

存在性

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

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

文件类型

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

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

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

[root@c7-server ~]# ls -l /dev/autofs 
crw------- 1 root root 10, 235 Jan  2 13:51 /dev/autofs
[root@c7-server ~]# [ -c /dev/autofs ]
[root@c7-server ~]# echo $?
0

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

[root@c7-server ~]# ls -ld /root
dr-xr-x---. 16 root root 4096 Jan  7 09:54 /root
[root@c7-server ~]# [ -d /root ]
[root@c7-server ~]# echo $?
0

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

复制代码
[root@c7-server ~]# ls -l /etc/passwd
-rw-r--r-- 1 root root 2296 Nov 11 14:28 /etc/passwd
[root@c7-server ~]# file /etc/passwd
/etc/passwd: ASCII text
[root@c7-server ~]# [ -f /etc/passwd ]
[root@c7-server ~]# echo $?
0
复制代码

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

-L FILE:同上。

复制代码
[root@c7-server ~]# ls -l /etc/rc.local 
lrwxrwxrwx. 1 root root 13 Oct 17 14:59 /etc/rc.local -> rc.d/rc.local
[root@c7-server ~]# [ -h /etc/rc.local ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ -L /etc/rc.local ]
[root@c7-server ~]# echo $?
0
复制代码

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

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

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

复制代码
[root@c7-server ~]# ls -l /run/systemd/shutdownd
srw------- 1 root root 0 Jan  7 09:54 /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 $?
0
复制代码

文件权限

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

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

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

复制代码
[root@c7-server ~]# ls -l /etc/passwd
-rw-r--r-- 1 root root 2296 Nov 11 14:28 /etc/passwd
[root@c7-server ~]# [ -r /etc/passwd ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ -w /etc/passwd ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ -x /etc/passwd ]
[root@c7-server ~]# echo $?
1
复制代码

特殊文件权限

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

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

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

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

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

[root@c7-server ~]# ls -ld /tmp/
drwxrwxrwt. 11 root root 4096 Jan  7 14:21 /tmp/
[root@c7-server ~]# [ -k /tmp/ ]
[root@c7-server ~]# echo $?
0

文件是否有内容测试

-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 $?
1
[root@c7-server ~]# [ -s /etc/passwd ]
[root@c7-server ~]# echo $?
0
复制代码

时间戳测试

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

复制代码
[root@c7-server ~]# cat test.txt
[root@c7-server ~]# [ -N test.txt ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# echo "alongdidi" > test.txt 
[root@c7-server ~]# [ -N test.txt ]
[root@c7-server ~]# echo $?
0
复制代码

从属关系测试

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

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

复制代码
[root@c7-server ~]# ls -ld /home/zwl/
drwx------. 3 zwl zwl 78 Apr 11  2018 /home/zwl/
[root@c7-server ~]# [ -O /home/zwl/ ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# [ -G /home/zwl/ ]
[root@c7-server ~]# echo $?
1
复制代码

双目测试

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

复制代码
[root@c7-server ~]# ln test.txt test.hard
[root@c7-server ~]# ls -li test.txt test.hard 
33731123 -rw-r--r-- 2 root root 10 Jan  7 14:31 test.hard
33731123 -rw-r--r-- 2 root root 10 Jan  7 14:31 test.txt
[root@c7-server ~]# [ test.txt -ef test.hard ]
[root@c7-server ~]# echo $?
0
复制代码

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

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

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

组合测试条件

即与或非。

[ 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 10
exit
[root@c7-server ~]# echo $?
10

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

复制代码
[root@c7-server ~]# exit 1000
exit
[root@c7-server ~]# echo $?
232
[root@c7-server ~]# bash
[root@c7-server ~]# exit 1024
exit
[root@c7-server ~]# echo $?
0
复制代码

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

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

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

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

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

 

脚本中的参数

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

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

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

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

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

复制代码
[root@c7-server ~]# cat test.sh 
#!/bin/bash
echo $1
echo $2
[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 2
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
6

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

复制代码
[root@c7-server ~]# cat test.sh 
#!/bin/bash
echo $0
[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、状态返回值、位置参数和特殊变量相似的内容:

[转帖]Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量

Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量 我自己接触Linux主要是大学学习的Turbolinux --> 根据《鸟哥的Linux私房菜:基础篇》(第三版) --> 马哥的就业班课程。给我的感觉是这些课程对于bash的讲解,理论上是不够的,但是限于时间、篇幅和精

[转帖]Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量

https://www.cnblogs.com/alongdidi/p/test_exitStatus_positionalAndSpecialParameter.html 我自己接触Linux主要是大学学习的Turbolinux --> 根据《鸟哥的Linux私房菜:基础篇》(第三版) --> 马

[转帖]Bash脚本编程学习笔记05:用户交互与脚本调试

https://www.cnblogs.com/alongdidi/p/read_and_bash_debug.html 用户交互 在《学习笔记04》中我们有提到位置参数,位置参数是用来向脚本传递参数的一种方式。还有一种方式,是read命令。 [root@c7-server ~]# read nam

[转帖]Bash脚本编程学习笔记02:脚本基础和bash配置文件

脚本基础 参考资料:Shell Scripts (Bash Reference Manual) 不严谨地说,编程语言根据代码运行的方式,可以分为两种方式: 编译运行:需要先将人类可识别的代码文件编译成机器可运行的二进制程序文件后,方可运行。例如C语言和Java语言。 解释运行:需要一个编程语言的解释

[转帖]Bash脚本编程学习笔记03:算术运算

https://www.cnblogs.com/alongdidi/p/bash_arithmetic_expression.html 简介 Bash所支持的算术运算和C语言是一样的,这里指的是操作符(operator)以及它们的优先级(precedence)、结合性(associativity)和

[转帖]Bash脚本编程学习笔记03:算术运算

https://www.cnblogs.com/alongdidi/p/bash_arithmetic_expression.html 简介 Bash所支持的算术运算和C语言是一样的,这里指的是操作符(operator)以及它们的优先级(precedence)、结合性(associativity)和

[转帖]Bash脚本编程学习笔记06:条件结构体

简介 在bash脚本编程中,条件结构体使用if语句和case语句两种句式。 if语句 单分支if语句 if TEST; then CMD fi TEST:条件判断,多数情况下可使用test命令来实现,返回值为0的话则执行CMD,否则就离开该条件结构体,脚本继续往下执行。 [root@c7-serve

[转帖]Bash脚本编程学习笔记07:循环结构体

https://www.cnblogs.com/alongdidi/p/bash_loop_construct.html 本篇中涉及到算术运算,使用了$[]这种我未在官方手册中见到的用法,但是确实可用的,在此前的博文《Bash脚本编程学习笔记03:算术运算》中我有说明不要使用,不过自己忘记了。大家还

[转帖]Bash脚本编程学习笔记08:函数

https://www.cnblogs.com/alongdidi/p/bash_function.html 官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在《Bash脚本编程学习笔记06:条件结构体》中最后所说的,我们应该把一些可能反复执行

[转帖]Bash脚本编程学习笔记09:数组

https://www.cnblogs.com/alongdidi/p/bash_array.html 数组简介 在bash脚本编程当中,变量是存储单个元素的内存空间;而数组是存储多个元素的一段连续的内存空间。 数组由数组名和下标构成,如下。 ARRAY_NAME[SUBSCRIPT] 数组按照下标