Fork me on GitHub

Project Notes

Bash if and test

All about if/then test constructs in Bash scripting

Notes

The if/then construct tests whether the exit status of a list of commands is 0, and if so, executes one or more commands.

Bash has a [ left bracket special character command. It is a synonym for test, and a builtin for efficiency reasons. This command considers its arguments as comparison expressions or file tests and returns an exit status corresponding to the result of the comparison (0 for true, 1 for false).

With version 2.02, Bash introduced the [[ ... ]] extended test command. Note that [[ is a keyword, not a command.

string variable tests

The main string comparison operators:

  • ` = ` : equal e.g. if [ "$a" = "$b" ]
  • ` == ` : equal e.g. if [ "$a" == "$b" ]. Same as = but with different behaviour within double-brackets:
    • [[ $a == z* ]] # True if $a starts with an ā€œzā€ (pattern matching).
    • [[ $a == "z*" ]] # True if $a is equal to z* (literal matching).
    • [ $a == z* ] # File globbing and word splitting take place.
    • [ "$a" == "z*" ] # True if $a is equal to z* (literal matching).
  • ` != ` : not equal e.g. if [ "$a" != "$b" ]
  • ` < ` : less than, aSCII sort order e.g. if [ "$a" \< "$b" ] or if [[ "$a" < "$b" ]]
  • ` > ` : greater than, ASCII sort order e.g. if [ "$a" \> "$b" ] or if [[ "$a" > "$b" ]]
  • ` -z ` : string is null, that is, has zero length e.g. if [ -z "$a" ]
  • ` -n ` : string is not null e.g. if [ -n "$a" ]

For null string tests, always quote the variable. If not quoted, will usually work but is unsafe depending on how the string expands.

The =~ operator performs regular expression pattern matching.

Running the example test_string_variables.sh script:

$ ./test_string_variables.sh

# = operator tests (equal):

Given: VAR_SET=canary and VAR_NOT_SET not defined
it passes using [ "${VAR_SET}" = "canary" ] # VAR_SET: canary
it passes using [[ "${VAR_SET}" = "canary" ]] # VAR_SET: canary
returns false -> correctly fails using [ "${VAR_NOT_SET=}" = "canary" ] # VAR_NOT_SET:
returns false -> correctly fails using [[ "${VAR_NOT_SET=}" = "canary" ]] # VAR_NOT_SET:

## == operator tests (equal):

Given: VAR_SET=canary and VAR_NOT_SET not defined
returns true -> it passes using [ "${VAR_SET}" == "canary" ] # VAR_SET: canary
returns true -> it passes using [[ "${VAR_SET}" == "canary" ]] # VAR_SET: canary
returns false -> correctly fails using [ "${VAR_NOT_SET}" == "canary" ] # VAR_NOT_SET:
returns false -> correctly fails using [[ "${VAR_NOT_SET}" == "canary" ]] # VAR_NOT_SET:

## == operator tests (pattern matching):

Given: VAR_SET=canary and VAR_NOT_SET not defined
returns true -> it passes using [[ "${VAR_SET}" == can* ]] # VAR_SET: canary
returns false -> correctly fails using [[ "${VAR_SET}" == cannot* ]] # VAR_SET: canary

## != operator tests:

Given: VAR_SET=canary and VAR_NOT_SET not defined
returns false -> correctly fails using [ "${VAR_SET}" != "canary" ] # VAR_SET: canary
returns false -> correctly fails using [[ "${VAR_SET}" != "canary" ]] # VAR_SET: canary
returns true -> it passes using [ "${VAR_NOT_SET}" != "canary" ] # VAR_NOT_SET:
returns true -> it passes using [[ "${VAR_NOT_SET}" != "canary" ]] # VAR_NOT_SET:

## -n operator tests (not null):

Given: VAR_SET=canary and VAR_NOT_SET not defined
returns true -> it passes using [ -n "${VAR_SET}" ] # VAR_SET: canary
returns true -> it passes using [[ -n "${VAR_SET}" ]] # VAR_SET: canary
returns false -> correctly fails using [ -n "${VAR_NOT_SET}" ] # VAR_NOT_SET:
returns false -> correctly fails using [[ -n "${VAR_NOT_SET}" ]] # VAR_NOT_SET:

## -z operator tests (is null):

Given: VAR_SET=canary and VAR_NOT_SET not defined
returns false -> correctly fails using [ -z "${VAR_SET}" ] # VAR_SET: canary
returns false -> correctly fails using [[ -z "${VAR_SET}" ]] # VAR_SET: canary
returns true -> it passes using [ -z "${VAR_NOT_SET}" ] # VAR_NOT_SET:
returns true -> it passes using [[ -z "${VAR_NOT_SET}" ]] # VAR_NOT_SET:

## =~ operator tests (regex matching):

Given: VAR_TEXT=a string with a canary in it
returns true -> it passes using [[ "${VAR_TEXT}" =~ .*canary.* ]]
returns true -> it passes using [[ "${VAR_TEXT}" =~ canary.* ]]
returns true -> it passes using [[ "${VAR_TEXT}" =~ canary ]]
returns true -> it passes using [[ "${VAR_TEXT}" =~ ca..ry ]]

arithmetic comparisons

The (( ... )) double parentheses and let ā€¦ constructs return an exit status, according to whether the arithmetic expressions they evaluate expand to a non-zero value. These arithmetic-expansion constructs may therefore be used to perform arithmetic comparisons.

File tests

The file test operators are used to test a whole range of file an driectory properties. Most commonly:

  • -e file exists
  • -f file is a regular file (not a directory or device file)
  • -d file is a directory
  • ! to negate any file test operator

Running the example test_files.sh script:

$ ./test_files.sh

# -e operator tests (file exists):
correctly returns true for [ -e "./test_files.sh" ]
correctly returns false for [ -e "./bogative.sh" ]

# ! -e operator tests (not file exists):
correctly returns true for [ ! -e "./bogative.sh" ]

# -f operator tests (is a file):
correctly returns true for [ -f "./test_files.sh" ]
correctly returns false for [ -f "../if_and_test" ]
correctly returns false for [ -f "./bogative.sh" ]

# -d operator tests (is a directory):
correctly returns true for [ -d "../if_and_test" ]
correctly returns false for [ -d "./test_files.sh" ]
correctly returns false for [ -d "./bogative.sh" ]

Credits and References

About LCK#171 Bash
Project Source on GitHub Return to the Project Catalog

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.