Bash 脚本中使用 if 语句

Bash 脚本的条件判断语法

if 语法

if 是最常用的条件判断结构, 只有符合给定条件时, 才会执行指定的命令。它的语法如下

单分支 if 语句

if 判断表达式; then
    statement1
    statement2
    .......
fi

双分支的 if 语句

if 判断表达式; then
    statement1
    statement2
    .....
else
    statement3
    statement4
fi

多分支的 if 语句

if 判断表达式1; then
    statement1
elif 判断表达式2; then
    statement2
elif 判断表达式3; then
    statement3
else
    statement4
fi

ifthen 写在同一行时, 需要分号 (;) 分隔。分号是 Bash 的命令分隔符。它们也可以写成两行, 这时不需要分号。

if true
then
  echo 'hello world'
fi

除了多行的写法, if 结构也可以写成单行

$ if true; then echo 'hello world'; fi
hello world

注意, if 关键字后面也可以是一条命令, 该条命令执行成功 (返回值 0), 就意味着判断条件成立。

$ if echo 'hi'; then echo 'hello world'; fi
hi
hello world

上面命令中, if后面是一条命令echo 'hi'。该命令会执行, 如果返回值是0, 则执行then的部分。

if 后面可以跟任意数量的命令。这时, 所有命令都会执行, 但是判断真假只看最后一个命令, 即使前面所有命令都失败, 只要最后一个命令返回 0, 就会执行 then 的部分。

$ if false; true; then echo 'hello world'; fi
hello world

上面例子中, if 后面有两条命令 (false;true;), 第二条命令 (true) 决定了 then 的部分是否会执行。

test 命令

注意, 第二种和第三种写法, 括号两端必须要有空格

# 写法一
test expression

# 写法二
[ expression ]

# 写法三
[[ expression ]]

上面三种形式是等价的, 但是第三种形式还支持正则判断, 前两种不支持。

上面的 expression 是一个表达式。

这个表达式为真, test 命令执行成功 (返回值为 0) 表达式为假, test 命令执行失败 (返回值为 1)。

test -f /etc/hosts
echo $?
0

[ -f /etc/hosts ]
echo $?
0

上面的例子中, test 命令采用两种写法, 判断 /etc/hosts 文件是否存在, 这两种写法是等价的。命令执行后, 返回值为 0, 表示该文件确实存在

例子

下面把 test 命令的三种形式, 用在 if 结构中, 判断一个文件是否存在。

# 写法一
if test -e /tmp/foo.txt ; then
  echo "Found foo.txt"
fi

# 写法二
if [ -e /tmp/foo.txt ] ; then
  echo "Found foo.txt"
fi

# 写法三
if [[ -e /tmp/foo.txt ]] ; then
  echo "Found foo.txt"
fi

判断表达式

if 关键字后面, 跟的是一个命令。这个命令可以是 test 命令, 也可以是其他命令。命令的返回值为 0 表示判断成立, 否则表示不成立。因为这些命令主要是为了得到返回值, 所以可以视为表达式。

整数比较

[ integer1 -eq integer2 ] # integer1 等于 integer2, 则为真
[ integer1 -ne integer2 ] # integer1 不等于 integer2, 则为真
[ integer1 -gt integer2 ] # integer1 大于 integer2, 则为真
[ integer1 -lt integer2 ] # integer1 小于 integer2, 则为真
[ integer1 -ge integer2 ] # integer1 大于或等于 integer2, 则为真
[ integer1 -le integer2 ] # integer1 小于或等于 integer2, 则为真

例子

先判断变量 $INT 是否为空, 然后判断是否为 0, 接着判断正负, 最后通过求余数判断奇偶

#!/bin/bash

INT=-5

if [ -z "$INT" ]; then
  echo "INT is empty." >&2
  exit 1
fi
if [ $INT -eq 0 ]; then
  echo "INT is zero."
else
  if [ $INT -lt 0 ]; then
    echo "INT is negative."
  else
    echo "INT is positive."
  fi
  if [ $((INT % 2)) -eq 0 ]; then
    echo "INT is even."
  else
    echo "INT is odd."
  fi
fi

字符串比较

[ string ]                # 如果 string 不为空 (长度大于 0), 则为真
[ -n string ]             # 如果字符串 string 的长度大于零, 则为真
[ -z string ]             # 如果字符串 string 的长度为零, 则为真
[ string1 = string2 ]     # 如果 string1 和 string2 相同, 则为真
[ string1 == string2 ]    # 等同于[ string1 = string2 ]
[ string1 != string2 ]    # 如果 string1 和 string2 不相同, 则为真
[ string1 '>' string2 ]   # 如果按照字典顺序 string1 排列在 string2 之后, 则为真
[ string1 '<' string2 ]   # 如果按照字典顺序 string1 排列在 string2 之前, 则为真
[ string1 \> string2 ]    # 用反斜杠转义
[ string1 \< string2 ]    # 用反斜杠转义

注意, test 命令内部的 ><, 必须用引号引起来 (或者是用反斜杠转义)。否则, 它们会被 shell 解释为重定向操作符

例子

首先确定 $ANSWER 字符串是否为空。如果为空, 就终止脚本, 并把退出状态设为 1。注意, 这里的 echo 命令把错误信息 There is no answer. 重定向到标准错误, 这是处理错误信息的常用方法。如果 $ANSWER 字符串不为空, 就判断它的值是否等于 yesno 或者 maybe

#!/bin/bash

ANSWER=maybe

if [ -z "$ANSWER" ]; then
  echo "There is no answer." >&2
  exit 1
fi

if [ "$ANSWER" = "yes" ]; then
  echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
  echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
  echo "The answer is MAYBE."
else
  echo "The answer is UNKNOWN."
fi

注意, 字符串判断时, 变量要放在双引号之中, 比如 [ -n "$COUNT" ], 否则变量替换成字符串以后, test 命令可能会报错, 提示参数过多。另外, 如果不放在双引号之中, 变量为空时, 命令会变成 [ -n ], 这时会判断为真。如果放在双引号之中, [ -n "" ] 就判断为假。

if 语句进行判断是否为空

这三种写法是等价的

[ "$name" = "" ]
[ ! "$name" ]
[ -z "$name" ]

逻辑运算符

通过逻辑运算, 可以把多个 test 判断表达式结合起来, 创造更复杂的判断。三种逻辑运算 AND, ORNOT, 都有自己的专用符号。

AND 逻辑与: # 符号 &&, 也可使用参数 -a
OR  逻辑或: # 符号 ||, 也可使用参数 -o
NOT 逻辑非: # 符号 !

例子

下面是一个 AND 的例子, 判断整数是否在某个范围之内

&& 用来连接两个判断条件: 大于等于 $MIN_VAL, 并且小于等于 $MAX_VAL

#!/bin/bash

MIN_VAL=1
MAX_VAL=100

INT=50

if [[ $INT -ge $MIN_VAL && $INT -le $MAX_VAL ]]; then
  echo "$INT is within $MIN_VAL to $MAX_VAL."
else
  echo "$INT is out of range."
fi

使用否定操作符 ! 时, 最好用圆括号确定转义的范围

test 命令内部使用的圆括号, 必须使用引号或者反斜杠转义, 否则会被 Bash 解释。

if [ ! \( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL \) ]; then
    echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
    echo "$INT is in range."
fi

使用 -a 连接两个判断条件不太直观, 一般推荐使用 && 代替, 上面的脚本可以改写成下面这样

if !([ $INT -ge $MIN_VAL ] && [ $INT -le $MAX_VAL ]); then
  echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
  echo "$INT is in range."
fi

正则表达式

[[ expression ]] 这种判断形式, 支持正则表达式

[[ string1 =~ regex ]]

上面的语法中, regex 是一个正则表示式, =~ 是正则比较运算符

例子

先判断变量 INT 的字符串形式, 是否满足 ^-?[0-9]+$ 的正则模式, 如果满足就表明它是一个整数

#!/bin/bash

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
  echo "INT is an integer."
  exit 0
else
  echo "INT is not an integer." >&2
  exit 1
fi

文件测试

以下表达式用来判断文件状态

[ -a file ]           # 如果 file 存在, 则为真
[ -b file ]           # 如果 file 存在并且是一个块 (设备) 文件, 则为真
[ -c file ]           # 如果 file 存在并且是一个字符 (设备) 文件, 则为真
[ -d file ]           # 如果 file 存在并且是一个目录, 则为真
[ -e file ]           # 如果 file 存在, 则为真
[ -f file ]           # 如果 file 存在并且是一个普通文件, 则为真
[ -g file ]           # 如果 file 存在并且设置了组 ID, 则为真
[ -G file ]           # 如果 file 存在并且属于有效的组 ID, 则为真
[ -h file ]           # 如果 file 存在并且是符号链接, 则为真
[ -k file ]           # 如果 file 存在并且设置了它的 "sticky bit", 则为真
[ -L file ]           # 如果 file 存在并且是一个符号链接, 则为真
[ -N file ]           # 如果 file 存在并且自上次读取后已被修改, 则为真
[ -O file ]           # 如果 file 存在并且属于有效的用户 ID, 则为真
[ -p file ]           # 如果 file 存在并且是一个命名管道, 则为真
[ -r file ]           # 如果 file 存在并且可读 (当前用户有可读权限), 则为真
[ -s file ]           # 如果 file 存在且其长度大于零, 则为真
[ -S file ]           # 如果 file 存在且是一个网络 socket, 则为真
[ -t fd ]             # 如果 fd 是一个文件描述符, 并且重定向到终端, 则为真 这可以用来判断是否重定向了标准输入/输出/错误。
[ -u file ]           # 如果 file 存在并且设置了 setuid 位, 则为真
[ -w file ]           # 如果 file 存在并且可写 (当前用户拥有可写权限), 则为真
[ -x file ]           # 如果 file 存在并且可执行 (当前用户有执行/搜索权限), 则为真
[ FILE1 -nt FILE2 ]   # 如果 FILE1 比 FILE2 的更新时间更近, 或者 FILE1 存在而 FILE2 不存在, 则为真
[ FILE1 -ot FILE2 ]   # 如果 FILE1 比 FILE2 的更新时间更旧, 或者 FILE2 存在而 FILE1 不存在, 则为真
[ FILE1 -ef FILE2 ]   # 如果 FILE1 和 FILE2 引用相同的设备和 inode 编号, 则为真

例子

$FILE 要放在双引号之中, 这样可以防止变量 $FILE 为空, 从而出错。因为 $FILE 如果为空, 这时 [ -e $FILE ] 就变成 [ -e ], 这会被判断为真。而 $FILE 放在双引号之中, [ -e "$FILE" ] 就变成 [ -e "" ], 这会被判断为假。

#!/bin/bash

FILE=~/.bashrc

if [ -e "$FILE" ]; then
  if [ -f "$FILE" ]; then
    echo "$FILE is a regular file."
  fi
  if [ -d "$FILE" ]; then
    echo "$FILE is a directory."
  fi
  if [ -r "$FILE" ]; then
    echo "$FILE is readable."
  fi
  if [ -w "$FILE" ]; then
    echo "$FILE is writable."
  fi
  if [ -x "$FILE" ]; then
    echo "$FILE is executable/searchable."
  fi
else
  echo "$FILE does not exist"
  exit 1
fi

算术判断

Bash 还提供了 ((...)) 作为算术条件, 进行算术运算的判断

if ((3 > 2)); then
  echo "true"
fi

注意, 算术判断不需要使用 test 命令, 而是直接使用 ((...)) 结构。这个结构的返回值, 决定了判断的真假。

如果算术计算的结果是非零值, 则表示判断成立。这一点跟命令的返回值正好相反, 需要小心。

$ if ((1)); then echo "It is true."; fi
It is true.

$ if ((0)); then echo "It is true."; else echo "it is false."; fi
It is false.

上面例子中, ((1)) 表示判断成立, ((0)) 表示判断不成立。

算术条件 ((...)) 也可以用于变量赋值, ((...)) 中变量是可以不使用 $ 来引用的

$ if (( foo = 5 ));then echo "foo is $foo"; fi
foo is 5

上面例子中, (( foo = 5 )) 完成了两件事情。首先把 5 赋值给变量 foo, 然后根据返回值 5, 判断条件为真。

注意, 赋值语句返回等号右边的值, 如果返回的是 0, 则判断为假。

$ if (( foo = 0 ));then echo "It is true.";else echo "It is false."; fi
It is false.

下面是用算术条件改写的数值判断脚本

#!/bin/bash

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
  if ((INT == 0)); then
    echo "INT is zero."
  else
    if ((INT < 0)); then
      echo "INT is negative."
    else
      echo "INT is positive."
    fi
    if (( ((INT % 2)) == 0)); then
      echo "INT is even."
    else
      echo "INT is odd."
    fi
  fi
else
  echo "INT is not an integer." >&2
  exit 1
fi

只要是算术表达式, 都能用于 ((...)) 语法

普通命令的逻辑运算

如果 if 结构使用的不是 test 命令, 而是普通命令, 比如上一节的 ((...)) 算术运算, 或者 test 命令与普通命令混用, 那么可以使用 Bash 的命令控制操作符 && (AND) 和 || (OR), 进行多个命令的逻辑运算。

# 对于 && 操作符, 先执行 command1, 只有 command1 执行成功后, 才会执行 command2
command1 && command2

# 对于 || 操作符, 先执行 command1, 只有 command1 执行失败后, 才会执行 command2
command1 || command2

创建一个名为 temp 的目录, 执行成功后, 才会执行第二个命令, 进入这个目录。

mkdir temp && cd temp

测试目录 temp 是否存在, 如果不存在, 就会执行第二个命令, 创建这个目录。这种写法非常有助于在脚本中处理错误。

[ -d temp ] || mkdir temp

如果 temp 子目录不存在, 脚本会终止, 并且返回值为 1

[ ! -d temp ] && exit 1

if&& 结合使用的写法

if [ condition ] && [ condition ]; then
  command
fi

例子

只有在指定文件里面, 同时存在搜索词 word1word2, 就会执行 if 的命令部分。

#! /bin/bash

filename=$1
word1=$2
word2=$3

if grep $word1 $filename && grep $word2 $filename
then
  echo "$word1 and $word2 are both in $filename."
fi

将一个 && 判断表达式, 改写成对应的 if 结构

[[ -d "$dir_name" ]] && cd "$dir_name" && rm *

# 等同于

if [[ ! -d "$dir_name" ]]; then
  echo "No such directory: '$dir_name'" >&2
  exit 1
fi

if ! cd "$dir_name"; then
  echo "Cannot cd to '$dir_name'" >&2
  exit 1
fi

if ! rm *; then
  echo "File deletion failed. Check results" >&2
  exit 1
fi

case 结构

case 结构用于多值判断, 可以为每个值指定对应的命令, 跟包含多个 elifif 结构等价。它的语法如下

expression 是一个表达式, pattern 是表达式的值或者一个模式, 可以有多条, 用来匹配多个值, 每条以两个分号 (;;) 结尾

case expression in
  pattern1 )
    statement1 ;;
  pattern2 )
    statement2 ;;
  ...
esac

最后一条匹配语句的模式是 *, 这个通配符可以匹配其他字符和没有输入字符的情况, 类似 ifelse 部分

#!/bin/bash

echo -n "输入一个1到3之间的数字 (包含两端) > "
read character
case $character in
  1 ) echo 1
    ;;
  2 ) echo 2
    ;;
  3 ) echo 3
    ;;
  * ) echo 输入不符合要求
esac

case 的匹配模式可以使用各种通配符, 下面是一些例子

  • a): 匹配 a
  • a|b): 匹配 ab
  • [[:alpha:]]): 匹配单个字母
  • ???): 匹配 3 个字符的单词
  • *.txt): 匹配 .txt 结尾
  • *): 匹配任意输入, 通常作为 case 结构的最后一个模式

使用通配符 [[:lower:]] | [[:upper:]] 匹配字母, [0-9] 匹配数字

#!/bin/bash

echo -n "输入一个字母或数字 > "
read character
case $character in
  [[:lower:]] | [[:upper:]] ) echo "输入了字母 $character"
                              ;;
  [0-9] )                     echo "输入了数字 $character"
                              ;;
  * )                         echo "输入不符合要求"
esac

Bash 4.0 之前, case 结构只能匹配一个条件, 然后就会退出 case 结构。Bash 4.0 之后, 允许匹配多个条件, 这时可以用 ;;& 继续判断条件块。使用 bash --version 检查 Bash 版本

#!/bin/bash
# test.sh

read -n 1 -p "Type a character > "
echo
case $REPLY in
  [[:upper:]])    echo "'$REPLY' is upper case." ;;&
  [[:lower:]])    echo "'$REPLY' is lower case." ;;&
  [[:alpha:]])    echo "'$REPLY' is alphabetic." ;;&
  [[:digit:]])    echo "'$REPLY' is a digit." ;;&
  [[:graph:]])    echo "'$REPLY' is a visible character." ;;&
  [[:punct:]])    echo "'$REPLY' is a punctuation symbol." ;;&
  [[:space:]])    echo "'$REPLY' is a whitespace character." ;;&
  [[:xdigit:]])   echo "'$REPLY' is a hexadecimal digit." ;;&
esac

执行上面的脚本, 会得到下面的结果

$ test.sh
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.

可以看到条件语句结尾添加了 ;;& 以后, 在匹配一个条件之后, 并没有退出 case 结构, 而是继续判断下一个条件


原文

shell中if语句的使用
条件判断 - Bash 脚本教程 - 网道
How do I check my bash version?

最后更新于 2024-11-09
使用 Hugo 构建
主题 StackJimmy 设计