一个if/then语句结构测试一个或多个命令的退出状态是否为0(因为在unix系统中0表示’执行成功’),
如果为0,就执行语句后面的命令。
Bash中有个专用的命令叫[(左中括号,bash特殊字符之一)。它是内置命令test别名,为提升效率
其同时也是bash的内置命令。该命令视其接受的参数为比较表达式或文件测试(测试文件是否存在、
文件类型、文件权限等)并且返回一个对应于比较结果的退出状态(如果比较或测试结果为真则返回0,否则返回1)。
在bash2.02版本中,bash新增了[[ … ]]叫扩展的test测试命令,其进行比较时更贴合其他编程语言
的风格。需要注意的是[[是一个bash关键字而非命令。bash视[[ $a -lt $b ]]为单个元素,返回一个退出状态。
[root@centos8 ~]#type [[
[[ is a shell keyword
[root@centos8 ~]#type [
[ is a shell builtin
[root@centos8 ~]#type test
test is a shell builtin
[root@centos8 ~]#which [
/usr/bin/[
[root@centos8 ~]#which test
/usr/bin/test
[root@centos8 ~]#a=3
[root@centos8 ~]#b=4
[root@centos8 ~]#[[ $a -lt $b ]]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#a=5
[root@centos8 ~]#[[ $a -lt $b ]]
[root@centos8 ~]#echo $?
1
(( 0 && 1 )) # 逻辑与
echo $? # 1 ***
# And so ...
let "num = (( 0 && 1 ))"
echo $num # 0
# But ...
let "num = (( 0 && 1 ))"
echo $? # 1 ***
(( 200 || 11 )) # 逻辑或
echo $? # 0 ***
# ...
let "num = (( 200 || 11 ))"
echo $num # 1
let "num = (( 200 || 11 ))"
echo $? # 0 ***
(( 200 | 11 )) # 逐位或
echo $? # 0 ***
# ...
let "num = (( 200 | 11 ))"
echo $num # 203
let "num = (( 200 | 11 ))"
echo $? # 0 ***
# "let" 结构和双圆括号的返回状态相同。
“let” 结构和双圆括号的返回状态相同。
注意:某个算术表达式的退出状态不是该算术表达式计算错误的值。
var=-2 && (( var+=2 )) # 此处算术表达式值为0,退出状态为1
echo $? # 1
var=-2 && (( var+=2 )) && echo $var # 此处由于算术表达式为0,退出状态为1;bash认为非0的退出状态是命令未执行成功,导致$$后面的echo命令不在执行
# Will not echo $var!
if cmp a b &> /dev/null # 压缩标准输出和错误输出.
then echo "Files a and b are identical."
else echo "Files a and b differ."
fi
# The very useful "if-grep" construct:
# -----------------------------------
if grep -q Bash file
then echo "File contains at least one occurrence of Bash."
fi
word=Linux
letter_sequence=inu
if echo "$word" | grep -q "$letter_sequence"
# The "-q" option to grep suppresses output.
# -q 选项不输出任何信息到标准输出
then
echo "$letter_sequence found in $word"
else
echo "$letter_sequence not found in $word"
fi
if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
then echo "Command succeeded."
else echo "Command failed."
fi
例7-1. 什么才是真?
#!/bin/bash
# Tip:
# 如果你不确定某个条件的结果,那最好在if测试结构中测试其。
echo
echo "Testing \"0\""
if [ 0 ] # zero
then
echo "0 is true."
else # Or else ...
echo "0 is false."
fi # 0 为真.
echo
echo "Testing \"1\""
if [ 1 ] # one
then
echo "1 is true."
else
echo "1 is false."
fi # 1 为真.
echo
echo "Testing \"-1\""
if [ -1 ] # -1
then
echo "-1 is true."
else
echo "-1 is false."
fi # -1 为真.
echo
echo "Testing \"NULL\""
if [ ] # NULL (空条件)
then
echo "NULL is true."
else
echo "NULL is false."
fi # NULL 空为假。
echo
echo "Testing \"xyz\""
if [ xyz ] # 随机字符串
then
echo "Random string is true."
else
echo "Random string is false."
fi # 随机字符串为真.
echo
echo "Testing \"\$xyz\""
if [ $xyz ] # 测试变量$xyz是否为空, 但是...
# $xyz只是一个未初始化的变量.
then
echo "Uninitialized variable is true."
else
echo "Uninitialized variable is false."
fi # 未初始化的字符串为假.
echo
echo "Testing \"-n \$xyz\""
if [ -n "$xyz" ] # 有点卖弄学问的做法.
then
echo "Uninitialized variable is true."
else
echo "Uninitialized variable is false."
fi # 同样未初始化的字符串为假.
echo
xyz= # 初始化了,但是值为空.
echo "Testing \"-n \$xyz\""
if [ -n "$xyz" ]
then
echo "Null variable is true."
else
echo "Null variable is false."
fi # 空变量为假.
echo
# 什么时候'假'为真呢?(When is "false" true?)
echo "Testing \"false\""
if [ "false" ] # 此处 "false" 只是一个字符串而已...
then
echo "\"false\" is true." #+ 结果为真啦啦啦.
else
echo "\"false\" is false."
fi # 这时候'假'为真.
echo
echo "Testing \"\$false\"" # 再次测试'假',此时的'假'为未初始化的变量.
if [ "$false" ]
then
echo "\"\$false\" is true."
else
echo "\"\$false\" is false."
fi # 此时测试结构为假.
# What would happen if we tested the uninitialized variable "$true"?
echo
exit 0
if [ condition-true ]
then
command 1
command 2
...
else # 或者Or else ...
# 在下面写测试条件为假时的代码.
command 3
command 4
...
fi
if [ -x "$filename" ]; then
if [ condition1 ]
then
command1
command2
command3
elif [ condition2 ]
# Same as else if
then
command4
command5
else
default-command
fi
bash$ type test
test is a shell builtin
bash$ type '['
[ is a shell builtin
bash$ type '[['
[[ is a shell keyword
bash$ type ']]'
]] is a shell keyword
bash$ type ']'
bash: type: ]: not found
例7-2.test,/usr/bin/test,[ ],和/usr/bin/[
#!/bin/bash
echo
if test -z "$1"
then
echo "No command-line arguments."
else
echo "First command-line argument is $1."
fi
echo
if /usr/bin/test -z "$1" # 和内置命令"test"等价.
# ^^^^^^^^^^^^^ # 指明了完整的路径.
then
echo "No command-line arguments."
else
echo "First command-line argument is $1."
fi
echo
if [ -z "$1" ] # 和上面的代码块功能相同.
# if [ -z "$1" # 该代码应该可以正常工作,但是...
#+ Bash说后面的右中括号必须带,哎.
then
echo "No command-line arguments."
else
echo "First command-line argument is $1."
fi
echo
if /usr/bin/[ -z "$1" ] # 和上面的代码块功能相同.
then
echo "No command-line arguments."
else
echo "First command-line argument is $1."
fi
echo
exit 0
file=/etc/passwd
if [[ -e $file ]]
then
echo "Password file exists."
fi
&&,||,<,>操作符
在’[[ ]]‘结构中适用,但是在’[ ]'结构中报错。# [[ 八进制/十六进制运算 ]]
# Thank you, Moritz Gronbach, for pointing this out.
decimal=15
octal=017 # = 15 (decimal)
hex=0x0f # = 15 (decimal)
if [ "$decimal" -eq "$octal" ]
then
echo "$decimal equals $octal"
else
echo "$decimal is not equal to $octal" # 结果是15不等于017
fi # 在单中括号结构中不计算 [ single brackets ]!
if [[ "$decimal" -eq "$octal" ]]
then
echo "$decimal equals $octal" # 15 等于 017
else
echo "$decimal is not equal to $octal"
fi # 双中括号中计算 [[ double brackets ]]!
if [[ "$decimal" -eq "$hex" ]]
then
echo "$decimal equals $hex" # 15 等于 0x0f
else
echo "$decimal is not equal to $hex"
fi # [[ $hexadecimal ]] 单独引用一个十六进制数,也会自动计算为十进制!
dir=/home/bozo
if cd "$dir" 2>/dev/null; then # "2>/dev/null"将会重定向标准错误.
echo "Now in $dir."
else
echo "Can't change to $dir."
fi
var1=20
var2=22
[ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
home=/home/bozo
[ -d "$home" ] || echo "$home directory does not exist."
例7-3.使用(( ))结构测试算术运算结果
#!/bin/bash
# arith-tests.sh
# Arithmetic tests.
# (( ... ))结构计算并测试数学运算表达
# (( ... ))结构对于数学运算表达式的测试结果退出状态与[ ... ]结构相反!
# (( ... ))结构中运算结果非0为真,运算结果为0时退出状态为假。
(( 0 ))
echo "Exit status of \"(( 0 ))\" is $?." # 1
(( 1 ))
echo "Exit status of \"(( 1 ))\" is $?." # 0
(( 5 > 4 )) # true
echo "Exit status of \"(( 5 > 4 ))\" is $?." # 0
(( 5 > 9 )) # false
echo "Exit status of \"(( 5 > 9 ))\" is $?." # 1
(( 5 == 5 )) # true
echo "Exit status of \"(( 5 == 5 ))\" is $?." # 0
# (( 5 = 5 )) gives an error message.
(( 5 - 5 )) # 0
echo "Exit status of \"(( 5 - 5 ))\" is $?." # 1
(( 5 / 4 )) # Division o.k.
echo "Exit status of \"(( 5 / 4 ))\" is $?." # 0
(( 1 / 2 )) # 除法结果小于1.
echo "Exit status of \"(( 1 / 2 ))\" is $?." # 小于1的结果被圆整为0.
# 1
(( 1 / 0 )) 2>/dev/null # 使用0作为除数非法.
# ^^^^^^^^^^^
echo "Exit status of \"(( 1 / 0 ))\" is $?." # 1
# ======================================= #
# (( ... )) 该结构也常常被用在 if-then 测试结构中.
var1=5
var2=4
if (( var1 > var2 ))
then #^ ^ Note: Not $var1, $var2. Why?
echo "$var1 is greater than $var2"
fi # 5 is greater than 4
exit 0
-e 测试文件是否存在
-a 同上,已经被弃用,不推荐使用
-f 测试文件是否为普通文件(不是文件夹或者设备文件)
-s 测试文件是否非空(大小不是0)
-d 测试文件是否是一个文件夹
-b 测试文件是否是一个块设备
-c 测试文件是否是一个字符设备
device0="/dev/sda2"
if [ -b "$device0" ]
then
echo "$device0 is a block device."
fi
# /dev/sda2 是一个块设备.
device1="/dev/ttyS1"
if [ -c "$device1" ]
then
echo "$device1 is a character device."
fi
# /dev/ttyS1 是一个字符设备.
-p 测试文件是否为管道文件.
function show_input_type()
{
[ -p /dev/fd/0 ] && echo PIPE || echo STDIN # 此处/dev/fd/0表示标准输入
}
show_input_type "Input" # STDIN 标准输入
echo "Input" | show_input_type # PIPE 管道
-h 测试文件是否是一个符号链接
-L 测试文件是否是一个符号
-S 测试文件是否是一个socket文
-t 测试文件(或者文件描述符)是否与某个终端关联
该测试可以测试脚本中的标准输入[ -t 0 ]或者标准输出[ -t 1 ]是否是一个终端。
-r 运行本测试的用户是否对文件有读权限
-w 写权限
-x 执行权限
-g 文件或者文件夹是否设置SGID标志位
如果某个文件或者文件夹上设置有SGID权限,那么在该文件夹下创建的文件属主为该文件夹的属主。
-u 文件或者文件夹是否设置suid
如果某个属于root的二进制文件上被root设置了SUID标志位,则不管是谁运行,该可执行文件都以root权限运行;
单某个可执行文件必须要访问系统硬件时此功能非常有用。如果缺失SUID标志位,这些二进制文件不能被非root用户运行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7NmYZ4x-1691108945997)(png/2019-10-21-12-40-31.png)]
如上图:有SUID标志位的文件权限模式的执行权限位标识为s而不再是x(rwx --> rws).
-k 测试sticky位是否设置
如果一个文件设置了sticky位,那么该问价会被保存在cache内便于访问
如果某个文件夹被设置了sticky位,那么该文件夹的写权限将会被限制。
设置sticky位后,其他用户的执行权限模式不在表现为x而是t(rwx --> rwt)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fLENSzNn-1691108945998)(png/2019-10-21-12-56-29.png)]
也就是说:如果一个用户对某个设置有sticky位的目录有读权限但不是该目录的属主,那么他只能删除该目录下其拥有
的文件。这样做可以防止用户在公共文件夹意外删除别人的文件,例如/tmp文件夹。(当然,root用户是可以删除和更改的)
-O 测试自己是否是文件属主
-G 测试文件的GID是否和自己相同
-N 测试文件自最后一次读以来是否被修改过
f1 -nt f2 测试文件f1是否是比f2新
f1 -ot f2 测试文件f1是否是比f2旧
f1 -ef f2 测试文件f1和f2是否都是同一个文件的硬链接
! 对上面所列的条件取反。(如果条件为空,返回真,如下面例子)
[root@centos8 ~]#if [[ ! ]]; then echo true; fi
true
例7-4. 测试失效的链接(Testing for broken links)
#!/bin/bash
# broken-link.sh
# Written by Lee bigelow
# Used in ABS Guide with permission.
# 该脚本找出失效的符号链接并引用后输出。以便于给xargs处理。
#+ 例如. sh broken-link.sh /somedir /someotherdir|xargs rm
# 只不过更加推荐下面的方法:
# find "somedir" -type l -print0|\
# xargs -r0 file|\
# grep "broken symbolic"|
# sed -e 's/^\|: *broken symbolic.*$/"/g'
#
# 注意: 谨慎对待 /proc 文件系统和任何循环链接
################################################################
# 下面的语句表示如果没有指定目录参数传给脚本就将路径设置为当前的工作
# 目录。否则使用指定的目录参数进行搜索。
######################
[ $# -eq 0 ] && directorys=`pwd` || directorys=$@
# 下面的函数检查传给脚本的目录中为符号链接并且不存在的文件,检查到后引用起来并打印。
# 如果是目录中的子目录,则将该目录再次传给函数检查。
##########
linkchk () {
for element in $1/*; do
[ -h "$element" -a ! -e "$element" ] && echo \"$element\"
[ -d "$element" ] && linkchk $element
# Of course, '-h' tests for symbolic link, '-d' for directory.
done
}
# 下面将每个传给脚本的合法目录参数传给linkchk()函数,如果不是合法目录,则打印错误信息和用法。
##################
for directory in $directorys; do
if [ -d $directory ]
then linkchk $directory
else
echo "$directory is not a directory"
echo "Usage: $0 dir1 dir2 ..."
fi
done
exit $?
一个二进制比较操作符号比较两个变量或者比较数量。整数或者字符串的比较使用特定的符号集合。
整数比较
-eq 是否相等
if [ "$a" -eq "$b" ]
-ne 是否不等
if [ "$a" -ne "$b" ]
-gt $a是否大于$b
if [ "$a" -gt "$b" ]
-ge $a是否大于或等于$b
if [ "$a" -ge "$b" ]
-lt $a是否小于$b
if [ "$a" -lt "$b" ]
-le $a是否小于或等于$b
if [ "$a" -le "$b" ]
< 小于(必须在双圆括号结构中)
(("$a" < "$b"))
<= 小于或等于(必须在双圆括号结构中)
(("$a" <= "$b"))
> 大于(必须在双圆括号结构中)
(("$a" > "$b"))
>= 大于或等于(必须在双圆括号结构中)
(("$a" >= "$b"))
= is equal to
if [ "$a" = "$b" ]
注意=两边的空格.
if [ "$a"="$b" ] # 注意:该写法与上面的不等价.
== is equal to
if [ "$a" == "$b" ]
该写法是 = 的近似写法.
[[ $a == z* ]] # 如果 $a 以字母"z"开头则为真(模式匹配).
[[ $a == "z*" ]] # 将"z*"视为普通字符串.
[ $a == z* ] # 此处会视为通配和单词分割.
[ "$a" == "z*" ] # 将"z*"视为普通字符串.
# Thanks, Stéphane Chazelas
!= 是否不等
if [ "$a" != "$b" ]
该操作符也可以在[[ ... ]]结构中参与模式匹配操作.
< 是否小于,以ASCII字符的顺序比较
if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]
注意: "<" 在 [ ] 结构中需要被转义.
> 是否大于,以ASCII字符的顺序比较
if [[ "$a" > "$b" ]]
if [ "$a" \> "$b" ]
-z 判断某字符串是否为空,也就是长度为0
String='' # Zero-length ("null") string variable.
if [ -z "$String" ]
then
echo "\$String is null."
else
echo "\$String is NOT null."
fi # $String is null.
-n 判断某字符串是否非空.
例 7-5. 算术运算和比较
#!/bin/bash
a=4
b=5
# Here "a" and "b" can be treated either as integers or strings.
# There is some blurring between the arithmetic and string comparisons,
#+ since Bash variables are not strongly typed.
# Bash permits integer operations and comparisons on variables
#+ whose value consists of all-integer characters.
# Caution advised, however.
echo
if [ "$a" -ne "$b" ] # 数学运算比较
then
echo "$a is not equal to $b"
echo "(arithmetic comparison)"
fi
echo
if [ "$a" != "$b" ] # 字符串比较
then
echo "$a is not equal to $b."
echo "(string comparison)"
# "4" != "5"
# ASCII 52 != ASCII 53
fi
# 在该例子中 "-ne" 和 "!=" 都可以.
echo
exit 0
例 7-6. 测试某字符串是否为空
#!/bin/bash
# str-test.sh: Testing null strings and unquoted strings,
#+ but not strings and sealing wax, not to mention cabbages and kings . . .
# Using if [ ... ]
# If a string has not been initialized, it has no defined value.
# 如果一个字符串没有被定义,其没有被定义的值,这种状态叫'null'
if [ -n $string1 ] # string1 没有被初始化或定义过.
then
echo "String \"string1\" is not null."
else
echo "String \"string1\" is null."
fi
# 此处认为$string1非空,即使它没有被初始化.
echo
# 再试试?
if [ -n "$string1" ] # 这次 $string1 被双引号引用起来.
then
echo "String \"string1\" is not null."
else
echo "String \"string1\" is null."
fi # 为空!
echo
if [ $string1 ] # 此处变量string未引起
then
echo "String \"string1\" is not null."
else
echo "String \"string1\" is null."
fi # 为空,正确.
# 最好还是引用起来需要测试的字符串,不管是变量还是字符串本身.
#
# As Stephane Chazelas points out,
# if [ $string1 ] has one argument, "]"
#该测试结果只有一个参数"]"
# if [ "$string1" ] has two arguments, the empty "$string1" and "]"
#该测试结果有两个参数空字符串"$string1"和"]"
echo
string1=initialized
if [ $string1 ] # 赋值后, $string1 .
then
echo "String \"string1\" is not null."
else
echo "String \"string1\" is null."
fi # 非空.
# 然而,最好还是引起来,因为...
string1="a = b"
if [ $string1 ]
then
echo "String \"string1\" is not null."
else
echo "String \"string1\" is null."
fi # 此处不引用导致错误!
exit 0 # Thank you, also, Florian Wisser, for the "heads-up".
例 7-7. zmore
#!/bin/bash
# zmore
# View gzipped files with 'more' filter.
E_NOARGS=85
E_NOTFOUND=86
E_NOTGZIP=87
if [ $# -eq 0 ] # same effect as: if [ -z "$1" ]
# $1 can exist, but be empty: zmore "" arg2 arg3
then
echo "Usage: `basename $0` filename" >&2
# Error message to stderr.
exit $E_NOARGS
# Returns 85 as exit status of script (error code).
fi
filename=$1
if [ ! -f "$filename" ] # 使用引号可以避免带空格的文件名出错.
then
echo "File $filename not found!" >&2 # Error message to stderr.
exit $E_NOTFOUND
fi
if [ ${filename##*.} != "gz" ]
# Using bracket in variable substitution.
then
echo "File $1 is not a gzipped file!"
exit $E_NOTGZIP
fi
zcat $1 | more
# Uses the 'more' filter.
# May substitute 'less' if desired.
exit $? # Script returns exit status of pipe.
# Actually "exit $?" is unnecessary, as the script will, in any case,
#+ return the exit status of the last command executed.
-a
logical and
exp1 -a exp2 returns true if both exp1 and exp2 are true.
-o
logical or
exp1 -o exp2 returns true if either exp1 or exp2 is true.
[[ condition1 && condition2 ]]
if [ "$expr1" -a "$expr2" ]
then
echo "Both expr1 and expr2 are true."
else
echo "Either expr1 or expr2 is false."
fi
[ 1 -eq 1 ] && [ -n "ècho true 1>&2`" ] # true
[ 1 -eq 2 ] && [ -n "ècho true 1>&2`" ] # (no output)
# ^^^^^^^ False condition. So far, everything as expected.
# However ...
[ 1 -eq 2 -a -n "ècho true 1>&2`" ] # true
# ^^^^^^^ False condition. So, why "true" output?
# Is it because both condition clauses within brackets evaluate?
[[ 1 -eq 2 && -n "ècho true 1>&2`" ]] # (no output)
# No, that's not it.
# Apparently && and || "short-circuit" while -a and -o do not.
a=3
if [ "$a" -gt 0 ]
then
if [ "$a" -lt 5 ]
then
echo "The value of \"a\" lies somewhere between 0 and 5."
fi
fi
# Same result as:
if [ "$a" -gt 0 ] && [ "$a" -lt 5 ]
then
echo "The value of \"a\" lies somewhere between 0 and 5."
fi
if [ -f $HOME/.Xclients ]; then
exec $HOME/.Xclients
elif [ -f /etc/X11/xinit/Xclients ]; then
exec /etc/X11/xinit/Xclients
else
# failsafe settings. Although we should never get here
# (we provide fallbacks in Xclients as well) it can't hurt.
xclock -geometry 100x100-5+5 &
xterm -geometry 80x50-50+150 &
if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then
netscape /usr/share/doc/HTML/index.html &
fi
fi