Conditional judgment

This chapter introduces the conditional judgment syntax of Bash script.

if structure

if is the most commonly used conditional judgment structure. The specified command will be executed only when the given condition is met. Its syntax is as follows.

if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi

This command is divided into three parts: if, elif and else. Among them, the latter two parts are optional.

After the if keyword is the main judgment condition, elif is used to add other judgment conditions when the main condition is not satisfied, and else is the part to be executed when all the conditions are not satisfied.

if test $USER = "foo"; then
  echo "Hello foo."
else
  echo "You are not foo."
fi

In the above example, the judgment condition is whether the environment variable $USER is equal to foo, if it is equal, it will output Hello foo., otherwise output other content.

When if and then are written on the same line, they need to be separated by semicolons. The semicolon is Bash's command separator. They can also be written in two lines, in which case a semicolon is not required.

if true
then
  echo'hello world'
fi

if false
then
  echo'it is false' # This line will not be executed
fi

In the above example, true and false are two special commands. The former represents a successful operation and the latter represents a failed operation. if true means that the command part will always be executed, and if false means that the command part will never be executed.

In addition to multi-line writing, the if structure can also be written as a single line.

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

$ if false; then echo "It's true."; fi

Note that the if keyword can also be followed by a command. If the command is executed successfully (return value 0), it means that the judgment condition is established.

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

In the above command, if is followed by a command echo'hi'. The command will be executed. If the return value is 0, then the part of then will be executed.

Any number of commands can be followed by if. At this time, all commands will be executed, but only the last command is judged for authenticity. Even if all the previous commands fail, as long as the last command returns 0, the part of then will be executed.

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

In the above example, there are two commands (false;true;) after if, and the second command (true) determines whether the part of then will be executed.

There can be multiple elif parts.

#!/bin/bash

echo -n "Enter a number between 1 and 3 (including both ends)> "
read character
if ["$character" = "1" ]; then
    echo 1
elif ["$character" = "2" ]; then
    echo 2
elif ["$character" = "3" ]; then
    echo 3
else
    echo input does not meet the requirements
fi

In the above example, if the user enters 3, it will be judged 3 times in a row.

test command

The judgment condition of the if structure generally uses the test command, which has three forms.

# Writing a
test expression

# Writing method two
[expression]

# Writing method three
[[ expression ]]

The above three forms are equivalent, but the third form also supports regular judgment, the first two do not.

The above expression is an expression. If this expression is true, the test command is executed successfully (return value is 0); if the expression is false, the test command fails to execute (return value is 1). Note that in the second and third ways, there must be a space between [ and ] and the inner expression.

$ test -f /etc/hosts
$ echo $?
0

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

In the above example, the test command uses two writing methods to determine whether the /etc/hosts file exists. These two writing methods are equivalent. After the command is executed, the return value is 0, indicating that the file does exist.

In fact, the character [ is an abbreviated form of the test command, which can be regarded as an independent command, which explains why there must be a space after it.

The following three forms of the test command are used in the if structure to determine whether a file exists.

# Writing a
if test -e /tmp/foo.txt; then
  echo "Found foo.txt"
fi

# Writing method two
if [-e /tmp/foo.txt]; then
  echo "Found foo.txt"
fi

# Writing method three
if [[ -e /tmp/foo.txt ]]; then
  echo "Found foo.txt"
fi

Judgment expression

The if keyword is followed by a command. This command can be a test command or other commands. The return value of the command is 0 which means the judgment is established, otherwise it means it is not established. Because these commands are mainly for getting the return value, they can be regarded as expressions.

Commonly used judgment expressions are as follows.

File Judgment

The following expression is used to judge the file status.

-[ -a file ]: If file exists, it is true. -[ -b file ]: If file exists and is a block (device) file, it is true. -[ -c file ]: If file exists and is a character (device) file, it is true. -[ -d file ]: If file exists and is a directory, it is true. -[ -e file ]: If file exists, it is true. -[ -f file ]: If file exists and is a normal file, it is true. -[ -g file ]: If file exists and the group ID is set, it is true. -[ -G file ]: If file exists and belongs to a valid group ID, it is true. -[ -h file ]: If file exists and is a symbolic link, it is true. -[ -k file ]: If file exists and its "sticky bit" is set, it is true. -[ -L file ]: If file exists and is a symbolic link, it is true. -[ -N file ]: If the file exists and has been modified since the last time it was read, it is true. -[ -O file ]: If file exists and belongs to a valid user ID, it is true. -[ -p file ]: If file exists and is a named pipe, it is true. -[ -r file ]: If the file exists and is readable (the current user has readable permission), it is true. -[ -s file ]: If file exists and its length is greater than zero, it is true. -[ -S file ]: If file exists and is a network socket, it is true. -[ -t fd ]: If fd is a file descriptor and redirected to the terminal, it is true. This can be used to determine whether standard input/output/error is redirected. -[ -u file ]: If file exists and the setuid bit is set, it is true. -[ -w file ]: If file exists and is writable (the current user has writable permission), it is true. -[ -x file ]: If file exists and is executable (effective user has execute/search permission), it is true. -[ file1 -nt file2 ]: If FILE1 is updated more recently than FILE2, or FILE1 exists but FILE2 does not exist, it is true. -[ file1 -ot file2 ]: If the update time of FILE1 is older than FILE2, or FILE2 exists but FILE1 does not exist, it is true. -[ FILE1 -ef FILE2 ]: If FILE1 and FILE2 refer to the same device and inode number, it is true.

The following is an example.

#!/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

In the above code, $FILE should be enclosed in double quotes. This can prevent $FILE from being empty, because at this time [ -e ] will be judged to be true. Putting it in double quotes will always return an empty string, and [ -e "" ] will be judged as false.

String judgment

The following expression is used to judge the string.

-[ string ]: If string is not empty (the length is greater than 0), it is judged to be true. -[ -n string ]: If the length of the string string is greater than zero, it is judged to be true. -[ -z string ]: If the length of the string string is zero, it is judged to be true. -[ string1 = string2 ]: If string1 and string2 are the same, it is judged as true. -[ string1 == string2 ] is equivalent to [ string1 = string2 ]. -[ string1 != string2 ]: If string1 and string2 are not the same, it is judged as true. -[ string1'>' string2 ]: If string1 is arranged after string2 in lexicographical order, it is judged to be true. -[ string1'<' string2 ]: If string1 is arranged before string2 in lexicographical order, it is judged to be true.

Note that the > and < inside the test command must be enclosed in quotation marks (or escaped with a backslash). Otherwise, they will be interpreted by the shell as redirection operators.

The following is an example.

#!/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

In the above code, first determine whether the $ANSWER string is empty. If it is empty, terminate the script and set the exit status to 1. Note that the echo command here redirects the error message There is no answer. to standard error, which is a common method of handling error messages. If the $ANSWER string is not empty, it is judged whether its value is equal to yes, no or maybe.

Note that when judging the string, the variable should be enclosed in double quotation marks, such as [ -n "$COUNT" ]. Otherwise, after the variable is replaced with a string, the test command may report an error, prompting that there are too many parameters. In addition, if it is not enclosed in double quotes, when the variable is empty, the command will become [ -n ], and it will be judged as true at this time. If placed in double quotes, [ -n "" ] is judged to be false.

Integer judgment

The following expressions are used to judge integers.

-[ integer1 -eq integer2 ]: If integer1 is equal to integer2, it is true. -[ integer1 -ne integer2 ]: If integer1 is not equal to integer2, it is true. -[ integer1 -le integer2 ]: If integer1 is less than or equal to integer2, it is true. -[ integer1 -lt integer2 ]: If integer1 is less than integer2, it is true. -[ integer1 -ge integer2 ]: If integer1 is greater than or equal to integer2, it is true. -[ integer1 -gt integer2 ]: If integer1 is greater than integer2, it is true.

The following is an example of usage.

#!/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

In the above example, first judge whether the variable $INT is empty, then judge whether it is 0, then judge whether it is positive or negative, and finally judge the parity by finding the remainder.

Regular judgment

[[ expression ]] This form of judgment supports regular expressions.

[[ string1 =~ regex ]]

In the above grammar, regex is a regular expression, and =~ is a regular comparison operator.

Below is an example.

#!/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

In the above code, first determine whether the string form of the variable INT satisfies the regular pattern of ^-?[0-9]+$. If it is satisfied, it means that it is an integer.

The logical operation of test judgment

Through logical operations, multiple test judgment expressions can be combined to create more complex judgments. The three logical operations AND, OR, and NOT all have their own special symbols.

-AND operation: the symbol &&, the parameter -a can also be used. -OR operation: the symbol ||, the parameter -o can also be used. -NOT operation: the symbol !.

The following is an example of AND to determine whether an integer is within a certain range.

#!/bin/bash

MIN_VAL=1
MAX_VAL=100

INT=50

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

In the above example, && is used to connect two judgment conditions: greater than or equal to $MIN_VAL, and less than or equal to $MAX_VAL.

When using the negation operator !, it is best to use parentheses to determine the scope of escape.

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

In the above example, the parentheses used inside the test command must be quoted or escaped, otherwise it will be interpreted by Bash.

Arithmetic judgment

Bash also provides ((...)) as an arithmetic condition to determine arithmetic operations.

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

After the above code is executed, true will be printed.

Note that the arithmetic judgment does not need to use the test command, but directly use the ((...)) structure. The return value of this structure determines the authenticity of the judgment.

If the result of the arithmetic calculation is a non-zero value, it means that the judgment is established. This is the opposite of the return value of the command, so be careful.

$ 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.

In the above example, ((1)) indicates that the judgment is established, and ((0)) indicates that the judgment is not established.

Arithmetic conditions ((...)) can also be used for variable assignment.

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

In the above example, (( foo = 5 )) accomplishes two things. First assign 5 to the variable foo, and then judge the condition to be true based on the return value 5.

Note that the assignment statement returns the value on the right side of the equal sign. If it returns 0, it is judged to be false.

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

The following is a numerical judgment script rewritten with arithmetic conditions.

#!/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

As long as it is an arithmetic expression, it can be used in the ((...)) grammar. For details, see the chapter "Arithmetic Operations in Bash".

Logic operation of ordinary commands

If the if structure does not use the test command, but ordinary commands, such as the arithmetic operation of ((...)) in the previous section, or the test command mixed with ordinary commands, then you can use Bash's Command control operators && (AND) and || (OR) perform logical operations of multiple commands.

$ command1 && command2
$ command1 || command2

For the && operator, first execute command1, and only after successful execution of command1, will execute command2. For the || operator, command1 is executed first, and command2 will be executed only after the execution of command1 fails.

$ mkdir temp && cd temp

The above command will create a directory named temp, and only after successful execution will the second command be executed to enter this directory.

$ [-d temp] || mkdir temp

The above command will test whether the directory temp exists, and if it does not exist, the second command will be executed to create this directory. This way of writing is very helpful in handling errors in scripts.

[! -d temp] && exit 1

In the above command, if the temp subdirectory does not exist, the script will terminate and the return value is 1.

The following is how to use if in combination with &&.

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

The following is an example.

#! /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

The above example is only in the specified file, and if the search terms word1 and word2 exist at the same time, the command part of if will be executed.

The following example demonstrates how to rewrite a && judgment expression into the corresponding if structure.

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

# Equivalent to

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 structure

The case structure is used for multi-value judgment. You can specify the corresponding command for each value, which is equivalent to the if structure containing multiple elif, but has better semantics. Its syntax is as follows.

case expression in
  pattern)
    commands ;;
  pattern)
    commands ;;
  ...
esac

In the above code, expression is an expression, pattern is the value of the expression or a pattern, there can be multiple, used to match multiple values, each ending with two semicolons (;).

#!/bin/bash

echo -n "Enter a number between 1 and 3 (including both ends)> "
read character
case $character in
  1) echo 1
    ;;
  2) echo 2
    ;;
  3) echo 3
    ;;
  *) Echo input does not meet the requirements
esac

In the above example, the pattern of the last matching sentence is *. This wildcard can match other characters and the case of no input characters, similar to the else part of if.

Here is another example.

#!/bin/bash

OS=$(uname -s)

case "$OS" in
  FreeBSD) echo "This is FreeBSD" ;;
  Darwin) echo "This is Mac OSX" ;;
  AIX) echo "This is AIX" ;;
  Minix) echo "This is Minix" ;;
  Linux) echo "This is Linux" ;;
  *) echo "Failed to identify this OS" ;;
esac

The above example determines what operating system is currently.

The matching pattern of case can use various wildcards. Here are some examples.

-a): matches a. -a|b): matches a or b. -[[:alpha:]]): Match a single letter. -???): Match a word of 3 characters. -*.txt): matches the end of .txt. -*): Match any input, passing as the last pattern of the case structure.

#!/bin/bash

echo -n "Enter a letter or number>"
read character
case $character in
  [[:lower:]] | [[:upper:]]) echo "I entered the letters $character"
                              ;;
  [0-9]) echo "I entered a number $character"
                              ;;
  *) echo "The input does not meet the requirements"
esac

In the above example, use the wildcard [[:lower:]] | [[:upper:]] to match letters, and [0-9] to match numbers.

Before Bash 4.0, the case structure could only match one condition, and then it would exit the case structure. After Bash 4.0, multiple conditions are allowed to match. At this time, you can use ;;& to terminate each condition block.

#!/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

Execute the above script, you will get the following result.

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

You can see that after adding ;;& at the end of the conditional statement, after matching a condition, it does not exit the case structure, but continues to judge the next condition.