Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concept: Conditionals #712

Merged
merged 6 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions concepts/conditionals/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"authors": [
"glennj"
],
"contributors": [
"IsaacG"
],
"blurb": "Bash control flow commands for branching, and the concept of truthiness"
}
174 changes: 174 additions & 0 deletions concepts/conditionals/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Introduction

## Truthiness in Bash

Bash does not have the concept of boolean values.
There are strings and numbers, and arrays of strings or numbers.
So how do conditional commands deal with true and false?

## Exit Status

Commands produce an _exit status_ when they end.
An exit status is an integer between 0 and 255 inclusive.
Bash considers a **zero** exit status to represent _success_.
Any other exit status represents _failure_.

This applies to all commands, including bash builtin commands, keywords, and functions.

## The "if" Command

The basic syntax of the `if` command is

```bash
if CONDITIONAL_COMMANDS; then TRUE_COMMANDS; else FALSE_COMMANDS; fi
IsaacG marked this conversation as resolved.
Show resolved Hide resolved
```

It starts with `if`; the condition commands are separated from the "success" commands by `then`; and it is terminated with `fi`.
The `else` clause is optional.

CONDITIONAL_COMMANDS can be a single command or it can be a [list of commands][command-list].
IsaacG marked this conversation as resolved.
Show resolved Hide resolved

The CONDITIONAL_COMMANDS are executed, and

* if the exit status is zero (success), then the TRUE_COMMANDS are executed.
* if the exit status is non-zero (failure), then the FALSE_COMMANDS are executed, if they are present.

Cascading branches can be given with `elif`
```bash
if CONDITIONAL_COMMANDS
then TRUE_COMMANDS
elif CONDITIONAL_COMMANDS_2
then TRUE_COMMANDS_2
# more elif branches ...
else FALSE_COMMANDS
fi
```

There must be a semicolon or a newline before the `then`, `elif`, `else` and `fi` words.

To emphasize: it is the **exit status** of the conditional commands that controls the flow.
As an example, `grep` returns 0 if a match is found, and 1 if a match is not found.
The `-q` option suppresses output, only producing the exit status.

```bash
if grep -q "my pattern" my_file; then echo "the pattern is found in the file"; fi
```

## "[" and "test" Commands

There is no special syntax around the CONDITIONAL_COMMANDS.
You may be used to seeing if statements that look like this:

```if
if [ "$password" = "secure" ]; then
echo "Welcome!"
fi
```

`[` is not special syntax.
It is a _command_ that evaluates the conditional expression and exits with a success/failure status.
Like all commands, whitespace is **required** between it and its arguments.

`[` is actually a synonym for the `test` command.
They are exactly the same, except that the last argument to `[` _must_ be `]`.

### Conditional Expressions

Within `[` and `]`, you write a conditional expression.
Some typical conditional expressions include:

```bash
[ -f "$filename" ] # file operations
[ "$string1" = "$string2" ] # string comparisons
[ "$num1" -eq "$num2" ] # arithmetic comparisons
```

There are many more operations available: they are listed in the [Bash Conditional Expressions][cond-expr] section of the manual.
glennj marked this conversation as resolved.
Show resolved Hide resolved

~~~~exercism/note
In the examples above, notice that all the variables are quoted.
The `test` and `[` commands are plain commands, where the arguments are subject to word splitting and filename expansion like any other command.

This is important to point out because conditional expressions are evaluated differently based on _how many arguments_ you provide:

* 0 arguments: the exit status is non-zero (failure)
* 1 argument: the exit status is zero (success) if the argument is not empty, non-zero if it is empty.
* 2 arguments: the first argument must be a unary operator (such as `[ -z "$name" ]`), or a `!` (negating the status of the 1-argument test)
* and [more][test].

You can get unexpected results if you forget to quote:

```bash
str=""

# this prints "empty"
if [ -n "$str" ]; then echo "not empty"; else echo "empty"; fi

# leaving the variable unquoted results in incorrect "not empty" output
if [ -n $str ]; then echo "not empty"; else echo "empty"; fi
```

[test]: https://www.gnu.org/software/bash/manual/bash.html#index-test
~~~~

## "[[" Keyword

The [`[[...]]` conditional construct][cond-construct] is not a _command_, it is a **keyword**.
This means that, although it is handled like any other command, it can have special parsing rules.
glennj marked this conversation as resolved.
Show resolved Hide resolved
What's special about `[[` is that the variables expanded within it are **not** subject to word splitting or filename expansion.
That means this command acts as you expect, even without quoting.

```bash
if [[ -n $str ]]; then echo "not empty"; else echo "empty"; fi
```

`[[` works with all the conditional expressions that `test` and `[` can handle.
glennj marked this conversation as resolved.
Show resolved Hide resolved
In addition, `[[` provides

* the `=~` regular-expression matching operator,
* `==` and `!=` operate as a glob-pattern matching operator,
* `&&` and `||` as logical operators (special parsing rule),
* `<` and `>` as "bare" string comparison operators (special parsing rule: because these are redirection symbols, in `[` they must be escaped).

It is widely held that these special features offer so much benefit that `[[` should be used exclusively.
(For example, the [Google Shell Style Guide][goog-test].)

## The "case" Command

[`case`][case] is another control flow command.
It is like a "switch" statement in other languages.

```bash
case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
```

The WORD is matched against each PATTERN.
When one matches, the COMMANDS are executed.

```bash
read -p "Guess the secret word: " word
case "$word" in
secret) echo "Yes, you guessed it!" ;;
??????) echo "That's the right number of letters." ;;
s*) echo "You guessed the first letter." ;;
*) echo "Not even close! Try again." ;;
esac
```

Each COMMANDS clause must end with **two** semicolons, `;;`.

~~~~exercism/note
1. There are alternatives that provide functionality for fall-through command execution, and for pattern matching to continue.
Check [the manual][case] for details.
2. Recall that we talked about patterns in the [Filename Expansion][glob] section of the Quoting concept.

[case]: https://www.gnu.org/software/bash/manual/bash.html#index-case
[glob]: https://exercism.org/tracks/bash/concepts/quoting#h-filename-expansion
~~~~

[command-list]: https://www.gnu.org/software/bash/manual/bash.html#Lists
[if]: https://www.gnu.org/software/bash/manual/bash.html#index-if
[case]: https://www.gnu.org/software/bash/manual/bash.html#index-case
[cond-expr]: https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions
[cond-construct]: https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b
[goog-test]: https://google.github.io/styleguide/shellguide.html#s6.3-tests
10 changes: 10 additions & 0 deletions concepts/conditionals/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"url": "https://www.gnu.org/software/bash/manual/bash.html#index-if",
"description": "The bash `if` command"
},
{
"url": "https://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Conditional_Blocks_.28if.2C_test_and_.5B.5B.29",
"description": "Conditional Blocks in the Bash Guide"
}
]
5 changes: 5 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,11 @@
"uuid": "548085e3-720b-425e-a6c7-85b96afef657",
"slug": "quoting",
"name": "The Importance of Quoting"
},
{
"uuid": "fcd13bb3-3557-4f3a-82d8-5ba588a51cf4",
"slug": "conditionals",
"name": "Conditionals"
}
],
"key_features": [
Expand Down
Loading