shell内建命令

  所谓 Shell 内建命令,就是由 Bash 自身提供的命令,而不是文件系统中的某个可执行文件。

  例如,用于进入或者切换目录的 cd 命令,虽然我们一直在使用它,但如果不加以注意很难意识到它与普通命令的性质是不一样的:该命令并不是某个外部文件,只要在 Shell 中你就一定可以运行这个命令。

  可以使用 type 来确定一个命令是否是内建命令:

  由此可见,cd 是一个 Shell 内建命令,而 ifconfig 是一个外部文件,它的位置是/sbin/ifconfig。

  还记得系统变量 $PATH 吗?$PATH 变量包含的目录中几乎聚集了系统中绝大多数的可执行命令,它们都是外部命令。

  通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘 I/O,还需要 fork 出一个单独的进程来执行,执行完成后再退出。而执行内建命令相当于调用当前 Shell 进程的一个函数。

  下表列出了 Bash Shell 中直接可用的内建命令。

命令说明
: 扩展参数列表,执行重定向操作
. 读取并执行指定文件中的命令(在当前 shell 环境中)
alias 为指定命令定义一个别名
bg 将作业以后台模式运行
bind 将键盘序列绑定到一个 readline 函数或宏
break 退出 for、while、select 或 until 循环
builtin 执行指定的 shell 内建命令
caller 返回活动子函数调用的上下文
cd 将当前目录切换为指定的目录
command 执行指定的命令,无需进行通常的 shell 查找
compgen 为指定单词生成可能的补全匹配
complete 显示指定的单词是如何补全的
compopt 修改指定单词的补全选项
continue 继续执行 for、while、select 或 until 循环的下一次迭代
declare 声明一个变量或变量类型。
dirs 显示当前存储目录的列表
disown 从进程作业表中刪除指定的作业
echo 将指定字符串输出到 STDOUT
enable 启用或禁用指定的内建shell命令
eval 将指定的参数拼接成一个命令,然后执行该命令
exec 用指定命令替换 shell 进程
exit 强制 shell 以指定的退出状态码退出
export 设置子 shell 进程可用的变量
fc 从历史记录中选择命令列表
fg 将作业以前台模式运行
getopts 分析指定的位置参数
hash 查找并记住指定命令的全路径名
help 显示帮助文件
history 显示命令历史记录
jobs 列出活动作业
kill 向指定的进程 ID(PID) 发送一个系统信号
let 计算一个数学表达式中的每个参数
local 在函数中创建一个作用域受限的变量
logout 退出登录 shell
mapfile 从 STDIN 读取数据行,并将其加入索引数组
popd 从目录栈中删除记录
printf 使用格式化字符串显示文本
pushd 向目录栈添加一个目录
pwd 显示当前工作目录的路径名
read 从 STDIN 读取一行数据并将其赋给一个变量
readarray 从 STDIN 读取数据行并将其放入索引数组
readonly 从 STDIN 读取一行数据并将其赋给一个不可修改的变量
return 强制函数以某个值退出,这个值可以被调用脚本提取
set 设置并显示环境变量的值和 shell 属性
shift 将位置参数依次向下降一个位置
shopt 打开/关闭控制 shell 可选行为的变量值
source 读取并执行指定文件中的命令(在当前 shell 环境中)
suspend 暂停 Shell 的执行,直到收到一个 SIGCONT 信号
test 基于指定条件返回退出状态码 0 或 1
times 显示累计的用户和系统时间
trap 如果收到了指定的系统信号,执行指定的命令
type 显示指定的单词如果作为命令将会如何被解释
typeset 声明一个变量或变量类型。
ulimit 为系统用户设置指定的资源的上限
umask 为新建的文件和目录设置默认权限
unalias 刪除指定的别名
unset 刪除指定的环境变量或 shell 属性
wait 等待指定的进程完成,并返回退出状态码

1、alias命令

  alisa 用来给命令创建一个别名。若直接输入该命令且不带任何参数,则列出当前 Shell 进程中使用了哪些别名。现在你应该能理解类似ll这样的命令为什么与ls -l的效果是一样的吧。

  下面让我们来看一下有哪些命令被默认创建了别名:

  例如:

  你看,为了让我们使用方便,Shell 会给某些命令默认创建别名。

  使用 alias 命令自定义别名的语法格式为:alias new_name='command'

  比如,一般的关机命令是shutdown-h now,写起来比较长,这时可以重新定义一个关机命令:alias myShutdown='shutdown -h now'

  再如,通过 date 命令可以获得当前的 UNIX 时间戳,具体写法为:alias timestamp='date +%s'

  在《Shell命令替换》一节中,我们使用date +%s计算脚本的运行时间,现在学了 alias,就可以简化代码了。

    #!/bin/bash
    alias timestamp='date +%s'
    begin=`timestamp`  
    sleep 20s
    finish=$(timestamp)
    difference=$((finish - begin))
    echo "run time: ${difference}s"

  运行脚本,20 秒后看到输出结果:run time: 20s
  别名只是临时的
  在代码中使用 alias 命令定义的别名只能在当前 Shell 进程中使用,在子进程和其它进程中都不能使用。当前 Shell 进程结束后,别名也随之消失。

  要想让别名对所有的 Shell 进程都有效,就得把别名写入 Shell 配置文件。Shell 进程每次启动时都会执行配置文件中的代码做一些初始化工作,将别名放在配置文件中,那么每次启动进程都会定义这个别名。不知道如何修改配置文件的读者请猛击《Shell配置文件的加载》《编写自己的Shell配置文件》。
  使用 unalias 命令删除别名
  使用 unalias 内建命令可以删除当前 Shell 进程中的别名。unalias 有两种使用方法:

        第一种用法是在命令后跟上某个命令的别名,用于删除指定的别名。
        第二种用法是在命令后接-a参数,删除当前 Shell 进程中所有的别名。

  同样,这两种方法都是在当前 Shell 进程中生效的。要想永久删除配置文件中定义的别名,只能进入该文件手动删除。

# 删除 ll 别名
[mozhiyan@localhost ~]$ unalias ll
# 再次运行该命令时,报“找不到该命令”的错误,说明该别名被删除了
[mozhiyan@localhost ~]$ ll
-bash: ll: command not found

 2、echo命令

  echo 是一个 Shell 内建命令,用来在终端输出字符串,并在最后默认加上换行符。echo 命令输出结束后默认会换行,如果不希望换行,可以加上-n参数,如下所示:    echo -n "hello word "

  输出转义字符

  默认情况下,echo 不会解析以反斜杠\开头的转义字符。比如,\n表示换行,echo 默认会将它作为普通字符对待,遇到\n时不会换行,我们可以添加-e参数来让 echo 命令解析转义字符。有了-e参数,我们也可以使用转义字符\c来强制 echo 命令不换行了。请看下面的例子:

 3、read命令

  read 是 Shell 内置命令,用来从标准输入中读取数据并赋值给变量。如果没有进行重定向,默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据。

  read 命令的用法为:read [-options] [variables]

  options表示选项,如下表所示;variables表示用来存储数据的变量,可以有一个,也可以有多个。

  options和variables都是可选的,如果没有提供变量名,那么读取的数据将存放到环境变量 REPLY 中。

选项说明
-a array 把读取的数据赋值给数组 array,从下标 0 开始。
-d delimiter 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)。
-e 在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。
-n num 读取 num 个字符,而不是整行字符。
-p prompt 显示提示信息,提示内容为 prompt。
-r 原样读取(Raw mode),不把反斜杠字符解释为转义字符。
-s 静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。
-t seconds 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败。
-u fd 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。

4、exit命令

  exit 是一个 Shell 内置命令,用来退出当前 Shell 进程,并返回一个退出状态;使用$?可以接收这个退出状态。

  exit 命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。

  一般情况下,退出状态为 0 表示成功,退出状态为非 0 表示执行失败(出错)了。

  exit 退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。

  Shell 进程执行出错时,可以根据退出状态来判断具体出现了什么错误,比如打开一个文件时,我们可以指定 1 表示文件不存在,2 表示文件没有读取权限,3 表示文件类型不对。

5、declare 和 typeset命令

  declare 和 typeset 都是 Shell 内建命令,它们的用法相同,都用来设置变量的属性。不过 typeset 已经被弃用了,建议使用 declare 代替。

  declare 命令的用法如下所示:declare [+/-] [aAfFgilprtux] [变量名=变量值]

  其中,-表示设置属性,+表示取消属性,aAfFgilprtux都是具体的选项,它们的含义如下表所示:

选项含义
-f [name] 列出之前由用户在脚本中定义的函数名称和函数体。
-F [name] 仅列出自定义函数名称。
-g name 在 Shell 函数内部创建全局变量。
-p [name] 显示指定变量的属性和值。
-a name 声明变量为普通数组。
-A name 声明变量为关联数组(支持索引下标为字符串)。
-i name  将变量定义为整数型。
-r name[=value]  将变量定义为只读(不可修改和删除),等价于 readonly name。
-x name[=value] 将变量设置为环境变量,等价于 export name[=value]。

6、Shell数学计算(算术运算,加减乘除运算)

  如果要执行算术运算(数学计算),就离不开各种运算符号,和其他编程语言类似,Shell 也有很多算术运算符,下面就给大家介绍一下常见的 Shell 算术运算符,如下表所示:

算术运算符说明/含义
+、- 加法(或正号)、减法(或负号)
*、/、% 乘法、除法、取余(取模)
** 幂运算
++、-- 自增和自减,可以放在变量的前面也可以放在变量的后面
!、&&、|| 逻辑非(取反)、逻辑与(and)、逻辑或(or)
<、<=、>、>= 比较符号(小于、小于等于、大于、大于等于)
==、!=、= 比较符号(相等、不相等;对于字符串,= 也可以表示相当于)
<<、>> 向左移位、向右移位
~、|、 &、^ 按位取反、按位或、按位与、按位异或
=、+=、-=、*=、/=、%= 赋值运算符,例如 a+=1 相当于 a=a+1,a-=1 相当于 a=a-1

  但是,Shell和其它编程语言不同,Shell 不能直接进行算数运算,必须使用数学计算命令,这让初学者感觉很困惑,也让有经验的程序员感觉很奇葩。

  在 Bash Shell 中,如果不特别指明,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。换句话说,Bash shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视为字符串,这一点和大部分的编程语言不同。

  要想让数学计算发挥作用,必须使用数学计算命令,Shell 中常用的数学计算命令如下表所示:

运算操作符/运算命令说明
(( )) 用于整数运算,效率很高,推荐使用
let 用于整数运算,和 (()) 类似。
$[] 用于整数运算,不如 (()) 灵活。
expr 可用于整数运算,也可以处理字符串。比较麻烦,需要注意各种细节,不推荐使用。
bc Linux下的一个计算器程序,可以处理整数和小数。Shell 本身只支持整数运算,想计算小数就得使用 bc 这个外部的计算器。
declare -i 将变量定义为整数,然后再进行数学运算时就不会被当做字符串了。功能有限,仅支持最基本的数学运算(加减乘除和取余),不支持逻辑运算、自增自减等,所以在实际开发中很少使用。

  如果大家时间有限,只学习 (()) 和 bc 即可,不用学习其它的了:(()) 可以用于整数计算,bc 可以小数计算。

  (1)(())运算符

  双小括号 (( )) 是 Bash Shell 中专门用来进行整数运算的命令,它的效率很高,写法灵活,是企业运维中常用的运算命令。

  注意:(( )) 只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。后续讲到的 bc 命令可以用于小数运算。

  双小括号 (( )) 的语法格式为:((表达式))

  通俗地讲,就是将数学运算表达式放在((和))之间。

  表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( )) 命令的执行结果。

  可以使用$获取 (( )) 命令的结果,这和使用$获得变量值是类似的。

运算操作符/运算命令说明
((a=10+66)
((b=a-15))
((c=a+b))
这种写法可以在计算完成后给变量赋值。以 ((b=a-15)) 为例,即将 a-15 的运算结果赋值给变量 c。
注意,使用变量时不用加$前缀,(( )) 会自动解析变量名。
a=$((10+66)
b=$((a-15))
c=$((a+b))
可以在 (( )) 前面加上$符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以 c=$((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c。
注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。
((a>7 && b==c)) (( )) 也可以进行逻辑运算,在 if 语句中常会使用逻辑运算。
echo $((a+10)) 需要立即输出表达式的运算结果时,可以在 (( )) 前面加$符号。
((a=3+5, b=a+10)) 对多个表达式同时进行计算。

  在 (( )) 中使用变量无需加上$前缀,(( )) 会自动解析变量名,这使得代码更加简洁,也符合程序员的书写习惯。

  (2)let命令

  let 命令和双小括号 (( )) 的用法是类似的,它们都是用来对整数进行运算。

  注意:和双小括号 (( )) 一样,let 命令也只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。

  Shell let 命令的语法格式为:let 表达式    或者     let '表达式'    或者     let "表达式"

  它们都等价于((表达式))。

  当表达式中含有 Shell 特殊字符(例如 |)时,需要用双引号" "或者单引号' '将表达式包围起来。

  和 (( )) 类似,let 命令也支持一次性计算多个表达式,并且以最后一个表达式的值作为整个 let 命令的执行结果。但是,对于多个表达式之间的分隔符,let 和 (( )) 是有区别的:

  let 命令以空格来分隔多个表达式;(( )) 以逗号,来分隔多个表达式。

  另外还要注意,对于类似let x+y这样的写法,Shell 虽然计算了 x+y 的值,但却将结果丢弃;若不想这样,可以使用let sum=x+y将 x+y 的结果保存在变量 sum 中。

  (3)$[]

  和 (())、let 命令类似,$[] 也只能进行整数运算。

  Shell $[] 的用法如下:$[表达式]

  $[] 会对表达式进行计算,并取得计算结果。如果表达式中包含了变量,那么你可以加$,也可以不加。

  (4)expr

  expr 是 evaluate expressions 的缩写,译为“表达式求值”。Shell expr 是一个功能强大,并且比较复杂的命令,它除了可以实现整数计算,还可以结合一些选项对字符串进行处理,例如计算字符串长度、字符串比较、字符串匹配、字符串提取等。

  Shell expr 对于整数计算的用法为:expr 表达式

  expr 对表达式的格式有几点特殊的要求:

    出现在表达式中的运算符、数字、变量和小括号的左右两边至少要有一个空格,否则会报错。

    有些特殊符号必须用反斜杠\进行转义(屏蔽其特殊含义),比如乘号*和小括号(),如果不用\转义,那么 Shell 会把它们误解为正则表达式中的符号(*对应通配符,()对应分组)。   

    使用变量时要加$前缀。

  (5)bc

  Bash Shell 内置了对整数运算的支持,但是并不支持浮点运算,而 Linux bc 命令可以很方便的进行浮点运算,当然整数运算也不再话下。

  bc 甚至可以称得上是一种编程语言了,它支持变量、数组、输入输出、分支结构、循环结构、函数等基本的编程元素,所以 Linux 手册中是这样来描述 bc 的:An arbitrary precision calculator language

  翻译过来就是“一个任意精度的计算器语言”。

  在终端输入bc命令,然后回车即可进入 bc 进行交互式的数学计算。在 Shell 编程中,我们也可以通过管道和输入重定向来使用 bc。

  bc 命令还有一些选项,可能你会用到,请看下表。

选项说明
-h | --help 帮助信息
-v | --version 显示命令版本信息
-l | --mathlib 使用标准数学库
-i | --interactive 强制交互
-w | --warn 显示 POSIX 的警告信息
-s | --standard 使用 POSIX 标准来处理
-q | --quiet 不显示欢迎信息

   除了变量,bc 还支持函数、循环结构、分支结构等常见的编程元素,它们和其它编程语言的语法类似。

    内置变量

    bc 有四个内置变量,我们在计算时会经常用到,如下表所示:

变量名作 用
scale 指定精度,也即小数点后的位数;默认为 0,也即不使用小数部分。
ibase 指定输入的数字的进制,默认为十进制。
obase 指定输出的数字的进制,默认为十进制。
last 或者 . 表示最近打印的数字

    内置函数

    除了内置变量,bc 还有一些内置函数,如下表所示:

函数名作用
s(x) 计算 x 的正弦值,x 是弧度值。
c(x) 计算 x 的余弦值,x 是弧度值。
a(x) 计算 x 的反正切值,返回弧度值。
l(x) 计算 x 的自然对数。
e(x) 求 e 的 x 次方。
j(n, x) 贝塞尔函数,计算从 n 到 x 的阶数。

    在 Shell 中使用 bc 计算器

    在 Shell 脚本中,我们可以借助管道或者输入重定向来使用 bc 计算器。

    管道是 Linux 进程间的一种通信机制,它可以将前一个命令(进程)的输出作为下一个命令(进程)的输入,两个命令之间使用竖线|分隔。

    通常情况下,一个命令从终端获得用户输入的内容,如果让它从其他地方(比如文件)获得输入,那么就需要重定向。

    此处我们并不打算展开讲解管道和重定向,不了解的小伙伴请自行百度。

    借助管道使用 bc 计算器

    如果读者希望直接输出 bc 的计算结果,那么可以使用下面的形式:echo "expression" | bc

    expression就是希望计算的数学表达式,它必须符合 bc 的语法,上面我们已经进行了介绍。在 expression 中,还可以使用 Shell 脚本中的变量。

    使用下面的形式可以将 bc 的计算结果赋值给 Shell 变量:variable=$(echo "expression" | bc)

    variable 就是变量名。

  Shell declare -i:将变量声明为整数类型

   在《Shell declare命令》一节中,我们已经讲解了 declare 命令的各种选项,为了让 Shell 进行整数运算,本节我们重点讲解-i选项。

  默认情况下,Shell 中每一个变量的值都是字符串(不了解的读者请猛击《Shell变量》),即使你给变量赋值一个数字,它其实也是字符串,所以在进行数学计算时会出错。

  使用 declare 命令的-i选项可以将一个变量声明为整数类型,这样在进行数学计算时就不会作为字符串处理了。

  此外,你也不能写类似echo $m+$n这样的语句,这种情况下 m、n 也会被视为字符串。

  总之,除了将参与运算的变量定义为整数,还得将承载结果的变量定义为整数,而且只能用整数类型的变量来承载运算结果,不能直接使用 echo 输出。

  和 (())、let、$[] 不同,declare -i的功能非常有限,仅支持最基本的数学运算(加减乘除和取余),不支持逻辑运算(比较运算、与运算、或运算、非运算),所以在实际开发中很少使用。

7、test

  test 是 Shell 内置命令,用来检测某个条件是否成立。test 通常和 if 语句一起使用,并且大部分 if 语句都依赖 test。

  test 命令有很多选项,可以进行数值、字符串和文件三个方面的检测。

  Shell test 命令的用法为:test expression

  当 test 判断 expression 成立时,退出状态为 0,否则为非 0 值。

  test 命令也可以简写为[],它的用法为:[ expression ]

  注意[]和expression之间的空格,这两个空格是必须的,否则会导致语法错误。[]的写法更加简洁,比 test 使用频率高。

   test 和 [] 是等价的,后续我们会交替使用 test 和 [],以让读者尽快熟悉。

  (1)与文件相关的test选项 

文件类型判断
选 项作 用
-b filename 判断文件是否存在,并且是否为块设备文件。
-c filename 判断文件是否存在,并且是否为字符设备文件。
-d filename 判断文件是否存在,并且是否为目录文件。
-e filename 判断文件是否存在。
-f filename 判断文件是否存在,井且是否为普通文件。
-L filename 判断文件是否存在,并且是否为符号链接文件。
-p filename 判断文件是否存在,并且是否为管道文件。
-s filename 判断文件是否存在,并且是否为非空。
-S filename 判断该文件是否存在,并且是否为套接字文件。

 

文件权限判断
选 项作 用
-r filename 判断文件是否存在,并且是否拥有读权限。
-w filename 判断文件是否存在,并且是否拥有写权限。
-x filename 判断文件是否存在,并且是否拥有执行权限。
-u filename 判断文件是否存在,并且是否拥有 SUID 权限。
-g filename 判断文件是否存在,并且是否拥有 SGID 权限。
-k filename 判断该文件是否存在,并且是否拥有 SBIT 权限。

 

文件比较
选 项作 用
filename1 -nt filename2 判断 filename1 的修改时间是否比 filename2 的新。
filename -ot filename2 判断 filename1 的修改时间是否比 filename2 的旧。
filename1 -ef filename2 判断 filename1 是否和 filename2 的 inode 号一致,可以理解为两个文件是否为同一个文件。这个判断用于判断硬链接是很好的方法

  (2)与数值比较相关的 test 选​项

选 项作 用
num1 -eq num2 判断 num1 是否和 num2 相等。
num1 -ne num2 判断 num1 是否和 num2 不相等。
num1 -gt num2 判断 num1 是否大于 num2 。
num1 -lt num2 判断 num1 是否小于 num2。
num1 -ge num2 判断 num1 是否大于等于 num2。
num1 -le num2 判断 num1 是否小于等于 num2。

   (3) 与字符串判断相关的 test 选项

选 项作 用
-z str 判断字符串 str 是否为空。
-n str 判断宇符串 str 是否为非空。
str1 = str2
str1 == str2
===是等价的,都用来判断 str1 是否和 str2 相等。
str1 != str2 判断 str1 是否和 str2 不相等。
str1 \> str2 判断 str1 是否大于 str2。\>>的转义字符,这样写是为了防止>被误认为成重定向运算符。
str1 \< str2 判断 str1 是否小于 str2。同样,\<也是转义字符。

  有C语言、C++、Python、Java 等编程经验的读者请注意,==、>、< 在大部分编程语言中都用来比较数字,而在 Shell 中,它们只能用来比较字符串,不能比较数字,这是非常奇葩的,大家要习惯。

  其次,不管是比较数字还是字符串,Shell 都不支持 >= 和 <= 运算符,切记。  

  (4)与逻辑运算相关的 test 选项

选 项 作 用
expression1 -a expression 逻辑与,表达式 expression1 和 expression2 都成立,最终的结果才是成立的。
expression1 -o expression2 逻辑或,表达式 expression1 和 expression2 有一个成立,最终的结果就成立。
!expression 逻辑非,对 expression 进行取反。

  在 test 中使用变量建议用双引号包围起来

  test 和 [] 都是命令,一个命令本质上对应一个程序或者一个函数。即使是一个程序,它也有入口函数,例如C语言程序的入口函数是 main(),运行C语言程序就从 main() 函数开始,所以也可以将一个程序等效为一个函数,这样我们就不用再区分函数和程序了,直接将一个命令和一个函数对应起来即可。

  有了以上认知,就很容易看透命令的本质了:使用一个命令其实就是调用一个函数,命令后面附带的选项和参数最终都会作为实参传递给函数。

  假设 test 命令对应的函数是 func(),使用test -z $str1命令时,会先将变量 $str1 替换成字符串:

  如果 $str1 是一个正常的字符串,比如 abc123,那么替换后的效果就是test -z abc123,调用 func() 函数的形式就是func("-z abc123")。test 命令后面附带的所有选项和参数会被看成一个整体,并作为实参传递进函数。

  如果 $str1 是一个空字符串,那么替换后的效果就是test -z ,调用 func() 函数的形式就是func("-z "),这就比较奇怪了,因为-z选项没有和参数成对出现,func() 在分析时就会出错。

  如果我们给 $str1 变量加上双引号,当 $str1 是空字符串时,test -z "$str1"就会被替换为test -z "",调用 func() 函数的形式就是func("-z \"\""),很显然,-z选项后面跟的是一个空字符串(\"表示转义字符),这样 func() 在分析时就不会出错了。

  所以,当你在 test 命令中使用变量时,我强烈建议将变量用双引号""包围起来,这样能避免变量为空值时导致的很多奇葩问题。

  总结

  test 命令比较奇葩,>、<、== 只能用来比较字符串,不能用来比较数字,比较数字需要使用 -eq、-gt 等选项;不管是比较字符串还是数字,test 都不支持 >= 和 <=。有经验的程序员需要慢慢习惯 test 命令的这些奇葩用法。

  对于整型数字的比较,我建议大家使用 (()),这在《Shell if else》中已经进行了演示。(()) 支持各种运算符,写法也符合数学规则,用起来更加方便,何乐而不为呢?

  几乎完全兼容 test ,并且比 test 更加强大,比 test 更加灵活的是[[ ]];[[ ]]不是命令,而是 Shell 关键字,下节《Shell [[]]》我们将会讲解。

8、[[]]

  [[ ]]是 Shell 内置关键字,它和 test 命令类似,也用来检测某个条件是否成立。

  test 能做到的,[[ ]] 也能做到,而且 [[ ]] 做的更好;test 做不到的,[[ ]] 还能做到。可以认为 [[ ]] 是 test 的升级版,对细节进行了优化,并且扩展了一些功能。

  [[ ]] 的用法为:[[ expression ]]

  当 [[ ]] 判断 expression 成立时,退出状态为 0,否则为非 0 值。注意[[ ]]和expression之间的空格,这两个空格是必须的,否则会导致语法错误。

  (1)[[ ]] 不需要注意某些细枝末节

  [[ ]] 是 Shell 内置关键字,不是命令,在使用时没有给函数传递参数的过程,所以 test 命令的某些注意事项在 [[ ]] 中就不存在了,具体包括:

  不需要把变量名用双引号""包围起来,即使变量是空值,也不会出错。
  不需要、也不能对 >、< 进行转义,转义后会出错。

  (2)[[ ]] 支持逻辑运算符

  对多个表达式进行逻辑运算时,可以使用逻辑运算符将多个 test 命令连接起来,例如:[ -z "$str1" ] || [ -z "$str2" ]

  你也可以借助选项把多个表达式写在一个 test 命令中,例如:[ -z "$str1" -o -z "$str2" ]

  但是,这两种写法都有点“别扭”,完美的写法是在一个命令中使用逻辑运算符将多个表达式连接起来。我们的这个愿望在 [[ ]] 中实现了,[[ ]]  支持 &&、|| 和 ! 三种逻辑运算符。

  使用 [[ ]] 对上面的语句进行改进:[[ -z $str1 || -z $str2 ]]

  这种写法就比较简洁漂亮了。

  注意,[[ ]] 剔除了 test 命令的-o和-a选项,你只能使用 || 和 &&。这意味着,你不能写成下面的形式:[[ -z $str1 -o -z $str2 ]]

  当然,使用逻辑运算符将多个 [[ ]] 连接起来依然是可以的,因为这是 Shell 本身提供的功能,跟 [[ ]] 或者 test 没有关系,如下所示:[[ -z $str1 ]] || [[ -z $str2 ]]

test 或 [] [[ ]]
[ -z "$str1" ] || [ -z "$str2" ] [[ -z $str1 ]] || [[ -z $str2 ]]
[ -z "$str1" -o -z "$str2" ] [[ -z $str1 -o -z $str2 ]] ×
[[ -z $str1 || -z $str2 ]] × [[ -z $str1 || -z $str2 ]]

  (3)[[ ]] 支持正则表达式

  在 Shell [[ ]] 中,可以使用=~来检测字符串是否符合某个正则表达式,它的用法为:[[ str =~ regex ]]

  str 表示字符串,regex 表示正则表达式。

  (4)总结

  有了 [[ ]],你还有什么理由使用 test 或者 [ ],[[ ]] 完全可以替代之,而且更加方便,更加强大。

  但是 [[ ]] 对数字的比较仍然不友好,所以我建议,以后大家使用 if 判断条件时,用 (()) 来处理整型数字,用 [[ ]] 来处理字符串或者文件。

 

posted @ 2025-08-16 17:25  孤情剑客  阅读(25)  评论(0)    收藏  举报