mirror of
https://github.com/flokoe/bash-hackers-wiki.git
synced 2024-11-25 07:43:42 +01:00
Fix Markdown formatting for classictest.md
This commit is contained in:
parent
505673ba61
commit
f79b5314b2
@ -6,51 +6,46 @@
|
|||||||
|
|
||||||
## General syntax
|
## General syntax
|
||||||
|
|
||||||
This command allows you to do various tests and sets its exit code to 0
|
This command allows you to do various tests and sets its exit code to 0 (`true`) or 1 (`false`) whenever such a test succeeds or not.
|
||||||
(*TRUE*) or 1 (*FALSE*) whenever such a test succeeds or not. Using this
|
Using this exit code, it's possible to let Bash react on the result of such a test, here by using the command in an if-statement:
|
||||||
exit code, it\'s possible to let Bash react on the result of such a
|
|
||||||
test, here by using the command in an if-statement:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# test if /etc/passwd exists
|
|
||||||
|
|
||||||
|
# test if /etc/passwd exists
|
||||||
if test -e /etc/passwd; then
|
if test -e /etc/passwd; then
|
||||||
echo "Alright man..." >&2
|
echo "Alright man..." >&2
|
||||||
else
|
else
|
||||||
echo "Yuck! Where is it??" >&2
|
echo "Yuck! Where is it??" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
The syntax of the test command is relatively easy. Usually it\'s the
|
The syntax of the test command is relatively easy.
|
||||||
command name \"`test`\" followed by a test type (here \"`-e`\" for
|
Usually it's the command name `test` followed by a test type (here `-e` for "file exists") followed by test-type-specific values (here the filename to check, `/etc/passwd`).
|
||||||
\"file exists\") followed by test-type-specific values (here the
|
|
||||||
filename to check, \"`/etc/passwd`\").
|
|
||||||
|
|
||||||
There\'s a second standardized command that does exactly the same: the
|
There's a second standardized command that does exactly the same: the command `[` – the difference just is that it's called `[` and the last argument to the command must be a `]`: It forms `[ <EXPRESSION> ]`.
|
||||||
command \"`[`\" - the difference just is that it\'s called \"`[`\" and
|
|
||||||
the last argument to the command must be a \"`]`\": It forms
|
|
||||||
\"`[ <EXPRESSION> ]`\"
|
|
||||||
|
|
||||||
Let\'s rewrite the above example to use it:
|
Let's rewrite the above example to use it:
|
||||||
|
|
||||||
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# test if /etc/passwd exists
|
|
||||||
|
|
||||||
|
# test if /etc/passwd exists
|
||||||
if [ -e /etc/passwd ]; then
|
if [ -e /etc/passwd ]; then
|
||||||
echo "Alright man..." >&2
|
echo "Alright man..." >&2
|
||||||
else
|
else
|
||||||
echo "Yuck! Where is it??" >&2
|
echo "Yuck! Where is it??" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
One might **think** now that these \"\[\" and \"\]\" belong to the
|
One might **think** now that these `[` and `]` belong to the syntax of Bash's if-clause: **No they don't! It's a simple, ordinary command, still!**
|
||||||
syntax of Bash\'s if-clause: **No they don\'t! It\'s a simple, ordinary
|
|
||||||
command, still!**
|
|
||||||
|
|
||||||
Another thing you have to remember is that if the test command wants one
|
Another thing you have to remember is that if the test command wants one parameter for a test, you have to give it one parameter.
|
||||||
parameter for a test, you have to give it one parameter. Let\'s check
|
Let's check for some of your music files:
|
||||||
for some of your music files:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
mymusic="/data/music/Van Halen/Van Halen - Right Now.mp3"
|
mymusic="/data/music/Van Halen/Van Halen - Right Now.mp3"
|
||||||
@ -61,306 +56,276 @@ for some of your music files:
|
|||||||
echo "No music today, sorry..." >&2
|
echo "No music today, sorry..." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
As you definitely noted, the filename contains spaces. Since we call a
|
As you definitely noted, the filename contains spaces.
|
||||||
normal ordinary command (\"test\" or \"\[\") the shell will word-split
|
Since we call a normal ordinary command (`test` or `[`) the shell will word-split the expansion of the variable `mymusic`:
|
||||||
the expansion of the variable `mymusic`: You need to quote it when you
|
You need to quote it when you don't want the `test`-command to complain about too many arguments for this test-type!
|
||||||
don\'t want the `test`-command to complain about too many arguments for
|
If you didn't understand it, please read the [article about words\...](/syntax/words).
|
||||||
this test-type! If you didn\'t understand it, please read the [article
|
|
||||||
about words\...](/syntax/words)
|
|
||||||
|
|
||||||
Please also note that the file-tests want **one filename** to test.
|
Please also note that the file-tests want **one filename** to test.
|
||||||
Don\'t give a glob (filename-wildcards) as it can expand to many
|
Don't give a glob (filename-wildcards) as it can expand to many filenames => **too many arguments!**
|
||||||
filenames =\> **too many arguments!**
|
|
||||||
|
|
||||||
[**Another common mistake**]{.underline} is to provide too **few**
|
**Another common mistake** is to provide too **few** arguments:
|
||||||
arguments:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
[ "$mystring"!="test" ]
|
[ "$mystring"!="test" ]
|
||||||
|
```
|
||||||
|
|
||||||
This provides exactly **one** test-argument to the command. With one
|
This provides exactly **one** test-argument to the command.
|
||||||
parameter, it defaults to the `-n` test: It tests if a provided string
|
With one parameter, it defaults to the `-n` test:
|
||||||
is empty (`FALSE`) or not (`TRUE`) - due to the lack of **spaces to
|
It tests if a provided string is empty (`FALSE`) or not (`TRUE`) - due to the lack of **spaces to separate the arguments** the shown command always ends `TRUE`!
|
||||||
separate the arguments** the shown command always ends `TRUE`!
|
|
||||||
|
|
||||||
Well, I addressed several basic rules, now let\'s see what the
|
Well, I addressed several basic rules, now let's see what the test-command can do for you.
|
||||||
test-command can do for you. The Bash test-types can be split into
|
The Bash test-types can be split into several sections: **file tests**, **string tests**, **arithmetic tests**, **misc tests**.
|
||||||
several sections: **file tests**, **string tests**, **arithmetic
|
Below, the tests marked with :warning: are non-standard tests (i.e. not in SUS/POSIX/etc..).
|
||||||
tests**, **misc tests**. Below, the tests marked with :!: are
|
|
||||||
non-standard tests (i.e. not in SUS/POSIX/etc..).
|
|
||||||
|
|
||||||
## File tests
|
## File tests
|
||||||
|
|
||||||
This section probably holds the most tests, I\'ll list them in some
|
This section probably holds the most tests, I'll list them in some logical order.
|
||||||
logical order. Since Bash 4.1, all tests related to permissions respect
|
Since Bash 4.1, all tests related to permissions respect ACLs, if the underlying filesystem/OS supports them.
|
||||||
ACLs, if the underlying filesystem/OS supports them.
|
|
||||||
|
|
||||||
Operator syntax Description
|
| Operator syntax | Description |
|
||||||
----------------------------- -------------------------------------------------------------------------------------------- --
|
| ------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||||
**-a** \<FILE\> True if \<FILE\> exists. :!: (not recommended, may collide with `-a` for `AND`, see below)
|
| **-a** <FILE\> | True if <FILE\> exists. :warning: (not recommended, may collide with `-a` for `AND`, see below) |
|
||||||
**-e** \<FILE\> True if \<FILE\> exists.
|
| **-e** <FILE\> | True if <FILE\> exists. |
|
||||||
**-f** \<FILE\> True, if \<FILE\> exists and is a **regular** file.
|
| **-f** <FILE\> | True, if <FILE\> exists and is a **regular** file. |
|
||||||
**-d** \<FILE\> True, if \<FILE\> exists and is a **directory**.
|
| **-d** <FILE\> | True, if <FILE\> exists and is a **directory**. |
|
||||||
**-c** \<FILE\> True, if \<FILE\> exists and is a **character special** file.
|
| **-c** <FILE\> | True, if <FILE\> exists and is a **character special** file. |
|
||||||
**-b** \<FILE\> True, if \<FILE\> exists and is a **block special** file.
|
| **-b** <FILE\> | True, if <FILE\> exists and is a **block special** file. |
|
||||||
**-p** \<FILE\> True, if \<FILE\> exists and is a **named pipe** (FIFO).
|
| **-p** <FILE\> | True, if <FILE\> exists and is a **named pipe** (FIFO). |
|
||||||
**-S** \<FILE\> True, if \<FILE\> exists and is a **socket** file.
|
| **-S** <FILE\> | True, if <FILE\> exists and is a **socket** file. |
|
||||||
**-L** \<FILE\> True, if \<FILE\> exists and is a **symbolic link**.
|
| **-L** <FILE\> | True, if <FILE\> exists and is a **symbolic link**. |
|
||||||
**-h** \<FILE\> True, if \<FILE\> exists and is a **symbolic link**.
|
| **-h** <FILE\> | True, if <FILE\> exists and is a **symbolic link**. |
|
||||||
**-g** \<FILE\> True, if \<FILE\> exists and has **sgid bit** set.
|
| **-g** <FILE\> | True, if <FILE\> exists and has **sgid bit** set. |
|
||||||
**-u** \<FILE\> True, if \<FILE\> exists and has **suid bit** set.
|
| **-u** <FILE\> | True, if <FILE\> exists and has **suid bit** set. |
|
||||||
**-r** \<FILE\> True, if \<FILE\> exists and is **readable**.
|
| **-r** <FILE\> | True, if <FILE\> exists and is **readable**. |
|
||||||
**-w** \<FILE\> True, if \<FILE\> exists and is **writable**.
|
| **-w** <FILE\> | True, if <FILE\> exists and is **writable**. |
|
||||||
**-x** \<FILE\> True, if \<FILE\> exists and is **executable**.
|
| **-x** <FILE\> | True, if <FILE\> exists and is **executable**. |
|
||||||
**-s** \<FILE\> True, if \<FILE\> exists and has size bigger than 0 (**not empty**).
|
| **-s** <FILE\> | True, if <FILE\> exists and has size bigger than 0 (**not empty**). |
|
||||||
**-t** \<fd\> True, if file descriptor \<fd\> is open and refers to a terminal.
|
| **-t** <fd\> | True, if file descriptor <fd\> is open and refers to a terminal. |
|
||||||
\<FILE1\> **-nt** \<FILE2\> True, if \<FILE1\> is **newer than** \<FILE2\> (mtime). :!:
|
| <FILE1\> **-nt** <FILE2\> | True, if <FILE1\> is **newer than** <FILE2\> (mtime). :warning: |
|
||||||
\<FILE1\> **-ot** \<FILE2\> True, if \<FILE1\> is **older than** \<FILE2\> (mtime). :!:
|
| <FILE1\> **-ot** <FILE2\> | True, if <FILE1\> is **older than** <FILE2\> (mtime). :warning: |
|
||||||
\<FILE1\> **-ef** \<FILE2\> True, if \<FILE1\> and \<FILE2\> refer to the **same device and inode numbers**. :!:
|
| <FILE1\> **-ef** <FILE2\> | True, if <FILE1\> and <FILE2\> refer to the **same device and inode numbers**. :warning: |
|
||||||
|
|
||||||
## String tests
|
## String tests
|
||||||
|
|
||||||
Operator syntax Description
|
| Operator syntax | Description |
|
||||||
-------------------------------- ------------------------------------------------------------------------------------------------------------------------------------
|
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
**-z** \<STRING\> True, if \<STRING\> is **empty**.
|
| **-z** <STRING\> | True, if <STRING\> is **empty**. |
|
||||||
**-n** \<STRING\> True, if \<STRING\> is **not empty** (this is the default operation).
|
| **-n** <STRING\> | True, if <STRING\> is **not empty** (this is the default operation). |
|
||||||
\<STRING1\> **=** \<STRING2\> True, if the strings are **equal**.
|
| <STRING1\> **=** <STRING2\> | True, if the strings are **equal**. |
|
||||||
\<STRING1\> **!=** \<STRING2\> True, if the strings are **not equal**.
|
| <STRING1\> **!=** <STRING2\> | True, if the strings are **not equal**. |
|
||||||
\<STRING1\> **\<** \<STRING2\> True if \<STRING1\> sorts **before** \<STRING2\> lexicographically (pure ASCII, not current locale!). Remember to escape! Use `\<`
|
| <STRING1\> **<** <STRING2\> | True if <STRING1\> sorts **before** <STRING2\> lexicographically (pure ASCII, not current locale!). Remember to escape! Use `\<` |
|
||||||
\<STRING1\> **\>** \<STRING2\> True if \<STRING1\> sorts **after** \<STRING2\> lexicographically (pure ASCII, not current locale!). Remember to escape! Use `\>`
|
| <STRING1\> **\>** <STRING2\> | True if <STRING1\> sorts **after** <STRING2\> lexicographically (pure ASCII, not current locale!). Remember to escape! Use `\>` |
|
||||||
|
|
||||||
## Arithmetic tests
|
## Arithmetic tests
|
||||||
|
|
||||||
Operator syntax Description
|
| Operator syntax | Description |
|
||||||
----------------------------------- ---------------------------------------------------------------------
|
| ------------------------------- | ------------------------------------------------------------------- |
|
||||||
\<INTEGER1\> **-eq** \<INTEGER2\> True, if the integers are **equal**.
|
| <INTEGER1\> **-eq** <INTEGER2\> | True, if the integers are **equal**. |
|
||||||
\<INTEGER1\> **-ne** \<INTEGER2\> True, if the integers are **NOT equal**.
|
| <INTEGER1\> **-ne** <INTEGER2\> | True, if the integers are **NOT equal**. |
|
||||||
\<INTEGER1\> **-le** \<INTEGER2\> True, if the first integer is **less than or equal** second one.
|
| <INTEGER1\> **-le** <INTEGER2\> | True, if the first integer is **less than or equal** second one. |
|
||||||
\<INTEGER1\> **-ge** \<INTEGER2\> True, if the first integer is **greater than or equal** second one.
|
| <INTEGER1\> **-ge** <INTEGER2\> | True, if the first integer is **greater than or equal** second one. |
|
||||||
\<INTEGER1\> **-lt** \<INTEGER2\> True, if the first integer is **less than** second one.
|
| <INTEGER1\> **-lt** <INTEGER2\> | True, if the first integer is **less than** second one. |
|
||||||
\<INTEGER1\> **-gt** \<INTEGER2\> True, if the first integer is **greater than** second one.
|
| <INTEGER1\> **-gt** <INTEGER2\> | True, if the first integer is **greater than** second one. |
|
||||||
|
|
||||||
## Misc syntax
|
## Misc syntax
|
||||||
|
|
||||||
Operator syntax Description
|
| Operator syntax | Description |
|
||||||
---------------------------- ------------------------------------------------------------------------------------------------------------------------------------
|
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
||||||
\<TEST1\> **-a** \<TEST2\> True, if \<TEST1\> **and** \<TEST2\> are true (AND). Note that `-a` also may be used as a file test (see above)
|
| <TEST1\> **-a** <TEST2\> | True, if <TEST1\> **and** <TEST2\> are true (AND). Note that `-a` also may be used as a file test (see above) |
|
||||||
\<TEST1\> **-o** \<TEST2\> True, if either \<TEST1\> **or** \<TEST2\> is true (OR).
|
| <TEST1\> **-o** <TEST2\> | True, if either \<TEST1\> **or** <TEST2\> is true (OR). |
|
||||||
**!** \<TEST\> True, if \<TEST\> is **false** (NOT).
|
| **!** <TEST\> | True, if <TEST\> is **false** (NOT). |
|
||||||
**(** \<TEST\> **)** Group a test (for precedence). **Attention:** In normal shell-usage, the \"(\" and \")\" must be escaped; use \"\\(\" and \"\\)\"!
|
| **(** <TEST\> **)** | Group a test (for precedence). **Attention:** In normal shell-usage, the `(` and `)` must be escaped; use `\(` and `\)`! |
|
||||||
**-o** \<OPTION_NAME\> True, if the [shell option](/internals/shell_options) \<OPTION_NAME\> is set.
|
| **-o** <OPTION_NAME\> | True, if the [shell option](/internals/shell_options) <OPTION_NAME\> is set. |
|
||||||
**-v** \<VARIABLENAME\> True if the variable \<VARIABLENAME\> has been set. Use `var[n]` for array elements.
|
| **-v** <VARIABLENAME\> | True if the variable <VARIABLENAME\> has been set. Use `var[n]` for array elements. |
|
||||||
**-R** \<VARIABLENAME\> True if the variable \<VARIABLENAME\> has been set and is a nameref variable (since 4.3-alpha)
|
| **-R** <VARIABLENAME\> | True if the variable <VARIABLENAME\> has been set and is a nameref variable (since 4.3-alpha) |
|
||||||
|
|
||||||
## Number of Arguments Rules
|
## Number of Arguments Rules
|
||||||
|
|
||||||
The `test` builtin, especially hidden under its `[` name, may seem
|
The `test` builtin, especially hidden under its `[` name, may seem simple but is in fact **causing a lot of trouble sometimes**.
|
||||||
simple but is in fact **causing a lot of trouble sometimes**. One of the
|
One of the difficulty is that the behaviour of `test` not only depends on its arguments but also on the **number of its arguments**.
|
||||||
difficulty is that the behaviour of `test` not only depends on its
|
|
||||||
arguments but also on the **number of its arguments**.
|
|
||||||
|
|
||||||
Here are the rules taken from the manual ([**Note:**]{.underline} This
|
Here are the rules taken from the manual:
|
||||||
is for the command `test`, for `[` the number of arguments is calculated
|
|
||||||
without the final `]`, for example `[ ]` follows the \"zero arguments\"
|
!!! note
|
||||||
rule):
|
This is for the command `test`, for `[` the number of arguments is calculated without the final `]`, for example `[ ]` follows the "zero arguments" rule.
|
||||||
|
|
||||||
- **0 arguments**
|
- **0 arguments**
|
||||||
- The expression is false.
|
- The expression is false.
|
||||||
- **1 argument**
|
- **1 argument**
|
||||||
- The expression is true if, and only if, the argument is not null
|
- The expression is true if, and only if, the argument is not null
|
||||||
- **2 arguments**
|
- **2 arguments**
|
||||||
- If the first argument is `!` (exclamation mark), the expression
|
- If the first argument is `!` (exclamation mark), the expression is true if, and only if, the second argument is null
|
||||||
is true if, and only if, the second argument is null
|
- If the first argument is one of the unary conditional operators listed above under the syntax rules, the expression is true if the unary test is true
|
||||||
- If the first argument is one of the unary conditional operators
|
- If the first argument is not a valid unary conditional operator, the expression is false
|
||||||
listed above under the syntax rules, the expression is true if
|
|
||||||
the unary test is true
|
|
||||||
- If the first argument is not a valid unary conditional operator,
|
|
||||||
the expression is false
|
|
||||||
- **3 arguments**
|
- **3 arguments**
|
||||||
- If the second argument is one of the binary conditional
|
- If the second argument is one of the binary conditional operators listed above under the syntax rules, the result of the expression is the result of the binary test using the first and third arguments as operands
|
||||||
operators listed above under the syntax rules, the result of the
|
- If the first argument is `!`, the value is the negation of the two-argument test using the second and third arguments
|
||||||
expression is the result of the binary test using the first and
|
- If the first argument is exactly `(` and the third argument is exactly `)`, the result is the one-argument test of the second argument. Otherwise, the expression is false. The `-a` and `-o` operators are considered binary operators in this case (**Attention:** This means the operator `-a` is not a file operator in this case!)
|
||||||
third arguments as operands
|
|
||||||
- If the first argument is `!`, the value is the negation of the
|
|
||||||
two-argument test using the second and third arguments
|
|
||||||
- If the first argument is exactly `(` and the third argument is
|
|
||||||
exactly `)`, the result is the one-argument test of the second
|
|
||||||
argument. Otherwise, the expression is false. The `-a` and `-o`
|
|
||||||
operators are considered binary operators in this case
|
|
||||||
(**Attention:** This means the operator `-a` is not a file
|
|
||||||
operator in this case!)
|
|
||||||
- **4 arguments**
|
- **4 arguments**
|
||||||
- If the first argument is `!`, the result is the negation of the
|
- If the first argument is `!`, the result is the negation of the three-argument expression composed of the remaining arguments. Otherwise, the expression is parsed and evaluated according to precedence using the rules listed above
|
||||||
three-argument expression composed of the remaining arguments.
|
|
||||||
Otherwise, the expression is parsed and evaluated according to
|
|
||||||
precedence using the rules listed above
|
|
||||||
- **5 or more arguments**
|
- **5 or more arguments**
|
||||||
- The expression is parsed and evaluated according to precedence
|
- The expression is parsed and evaluated according to precedence using the rules listed above
|
||||||
using the rules listed above
|
|
||||||
|
|
||||||
These rules may seem complex, but it\'s not so bad in practice. Knowing
|
These rules may seem complex, but it's not so bad in practice.
|
||||||
them might help you to explain some of the \"unexplicable\" behaviours
|
Knowing them might help you to explain some of the "unexplicable" behaviours you might encounter:
|
||||||
you might encounter:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
var=""
|
var=""
|
||||||
if [ -n $var ]; then echo "var is not empty"; fi
|
if [ -n $var ]; then echo "var is not empty"; fi
|
||||||
|
```
|
||||||
|
|
||||||
This code prints \"var is not empty\", even though `-n something` is
|
This code prints "var is not empty", even though `-n something` is supposed to be true if `$var` is not empty - **why?**
|
||||||
supposed to be true if `$var` is not empty - **why?**
|
|
||||||
|
|
||||||
Here, as `$var` is **not quoted**, word splitting occurs and `$var`
|
Here, as `$var` is **not quoted**, word splitting occurs and `$var` results in actually nothing (Bash removes it from the command's argument list!).
|
||||||
results in actually nothing (Bash removes it from the command\'s
|
So the test is in fact `[ -n ]` **and falls into the "one argument" rule**, the only argument is "-n" which is not null and so the test returns true.
|
||||||
argument list!). So the test is in fact `[ -n ]` **and falls into the
|
The solution, as usual, is to **quote the parameter expansion**:
|
||||||
\"one argument\" rule**, the only argument is \"-n\" which is not null
|
`[ -n "$var" ]` so that the test has always 2 arguments, even if the second one is the null string.
|
||||||
and so the test returns true. The solution, as usual, is to **quote the
|
|
||||||
parameter expansion**: `[ -n "$var" ]` so that the test has always 2
|
|
||||||
arguments, even if the second one is the null string.
|
|
||||||
|
|
||||||
These rules also explain why, for instance, -a and -o can have several
|
These rules also explain why, for instance, -a and -o can have several meanings.
|
||||||
meanings.
|
|
||||||
|
|
||||||
## AND and OR
|
## AND and OR
|
||||||
|
|
||||||
### The Prefered Way
|
### The Prefered Way
|
||||||
|
|
||||||
The way often recommended to logically connect several tests with AND
|
The way often recommended to logically connect several tests with AND and OR is to use **several single test commands** and to **combine** them with the shell `&&` and `||` **list control operators**.
|
||||||
and OR is to use **several single test commands** and to **combine**
|
|
||||||
them with the shell `&&` and `||` **list control operators**.
|
|
||||||
|
|
||||||
See this:
|
See this:
|
||||||
|
|
||||||
|
```bash
|
||||||
if [ -n "$var"] && [ -e "$var"]; then
|
if [ -n "$var"] && [ -e "$var"]; then
|
||||||
echo "\$var is not null and a file named $var exists!"
|
echo "\$var is not null and a file named $var exists!"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
The return status of AND and OR lists is the exit status of the last
|
The return status of AND and OR lists is the exit status of the last command executed in the list
|
||||||
command executed in the list
|
|
||||||
|
|
||||||
- With `command1 && command2`, `command2` is executed if, and only if,
|
- With `command1 && command2`, `command2` is executed if, and only if, `command1` returns an exit status of zero (true)
|
||||||
`command1` returns an exit status of zero (true)
|
- With `command1 ││ command2`, `command2` is executed if, and only if, `command1` returns a non-zero exit status (false)
|
||||||
- With `command1 ││ command2`, `command2` is executed if, and only if,
|
|
||||||
`command1` returns a non-zero exit status (false)
|
|
||||||
|
|
||||||
### The other way: -a and -o
|
### The other way: -a and -o
|
||||||
|
|
||||||
The logical operators AND and OR for the test-command itself are `-a`
|
The logical operators AND and OR for the test-command itself are `-a` and `-o`, thus:
|
||||||
and `-o`, thus:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
if [ -n "$var" -a -e "$var" ] ; then
|
if [ -n "$var" -a -e "$var" ] ; then
|
||||||
echo "\$var is not null and a file named $var exists"
|
echo "\$var is not null and a file named $var exists"
|
||||||
fi
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
They are **not** `&&` or `||`:
|
They are **not** `&&` or `||`:
|
||||||
|
|
||||||
|
```bash
|
||||||
$ if [ -n "/tmp" && -d "/tmp"]; then echo true; fi # DOES NOT WORK
|
$ if [ -n "/tmp" && -d "/tmp"]; then echo true; fi # DOES NOT WORK
|
||||||
bash: [: missing `]'
|
bash: [: missing `]'
|
||||||
|
```
|
||||||
|
|
||||||
You might find the error message confusing, `[` does not find the
|
You might find the error message confusing, `[` does not find the required final `]`, because as seen above `&&` is used to write a **list of commands**.
|
||||||
required final `]`, because as seen above `&&` is used to write a **list
|
The `if` statement actually **sees two commands**:
|
||||||
of commands**. The `if` statement actually **sees two commands**:
|
|
||||||
|
|
||||||
- `[ -n "/tmp"`
|
- `[ -n "/tmp"`
|
||||||
- `-d "/tmp" ]`
|
- `-d "/tmp" ]`
|
||||||
|
|
||||||
\...which **must** fail.
|
...which **must** fail.
|
||||||
|
|
||||||
### Why you should avoid using -a and -o
|
### Why you should avoid using -a and -o
|
||||||
|
|
||||||
#### If portability is a concern
|
#### If portability is a concern
|
||||||
|
|
||||||
POSIX(r)/SUSv3 does **not** specify the behaviour of `test` in cases
|
POSIX(r)/SUSv3 does **not** specify the behaviour of `test` in cases where there are more than 4 arguments.
|
||||||
where there are more than 4 arguments. If you write a script that might
|
If you write a script that might not be executed by Bash, the behaviour might be different! [^1]
|
||||||
not be executed by Bash, the behaviour might be different! [^1]
|
|
||||||
|
|
||||||
#### If you want the cut behaviour
|
#### If you want the cut behaviour
|
||||||
|
|
||||||
Let\'s say, we want to check the following two things (AND):
|
Let's say, we want to check the following two things (AND):
|
||||||
|
|
||||||
1. if a string is null (empty)
|
1. if a string is null (empty)
|
||||||
2. if a command produced an output
|
2. if a command produced an output
|
||||||
|
|
||||||
Let\'s see:
|
Let\'s see:
|
||||||
|
|
||||||
|
```bash
|
||||||
if [ -z "false" -a -z "$(echo I am executed >&2)" ] ; then ...
|
if [ -z "false" -a -z "$(echo I am executed >&2)" ] ; then ...
|
||||||
|
```
|
||||||
|
|
||||||
=\> The arguments are all expanded **before** `test` runs, thus the
|
=> The arguments are all expanded **before** `test` runs, thus the echo-command **is executed**.
|
||||||
echo-command **is executed**.
|
|
||||||
|
|
||||||
|
```bash
|
||||||
if [ -z "false" ] && [ -z "$(echo I am not executed >&2)" ]; then...
|
if [ -z "false" ] && [ -z "$(echo I am not executed >&2)" ]; then...
|
||||||
|
```
|
||||||
|
|
||||||
=\> Due to the nature of the `&&` list operator, the second test-command
|
=> Due to the nature of the `&&` list operator, the second test-command runs only if the first test-command returns true, our echo-command **is not executed**.
|
||||||
runs only if the first test-command returns true, our echo-command **is
|
|
||||||
not executed**.
|
|
||||||
|
|
||||||
[**Note:**]{.underline} In my opinion, `-a` and `-o` are also less
|
!!! note
|
||||||
readable `[pgas]`
|
In my opinion, `-a` and `-o` are also less readable `[pgas]`
|
||||||
|
|
||||||
### Precedence and Parenthesis
|
### Precedence and Parenthesis
|
||||||
|
|
||||||
Take care if you convert your scripts from using `-a` and `-o` to use
|
Take care if you convert your scripts from using `-a` and `-o` to use the list way (`&&` and `||`):
|
||||||
the list way (`&&` and `||`):
|
|
||||||
|
|
||||||
- in the test-command rules, `-a` has **precedence over** `-o`
|
- in the test-command rules, `-a` has **precedence over** `-o`
|
||||||
- in the shell grammar rules, `&&` and `||` have **equal precedence**
|
- in the shell grammar rules, `&&` and `||` have **equal precedence**
|
||||||
|
|
||||||
That means, **you can get different results**, depending on the manner
|
That means, **you can get different results**, depending on the manner of use:
|
||||||
of use:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
$ if [ "true" ] || [ -e /does/not/exist ] && [ -e /does/not/exist ]; then echo true; else echo false; fi
|
$ if [ "true" ] || [ -e /does/not/exist ] && [ -e /does/not/exist ]; then echo true; else echo false; fi
|
||||||
false
|
false
|
||||||
|
|
||||||
$ if [ "true" -o -e /does/not/exist -a -e /does/not/exist ]; then echo true; else echo false;fi
|
$ if [ "true" -o -e /does/not/exist -a -e /does/not/exist ]; then echo true; else echo false;fi
|
||||||
true
|
true
|
||||||
|
```
|
||||||
|
|
||||||
As a result you have to think about it a little or add precedence
|
As a result you have to think about it a little or add precedence control (parenthesis).
|
||||||
control (parenthesis).
|
|
||||||
|
|
||||||
For `&&` and `||` parenthesis means (shell-ly) grouping the commands,
|
For `&&` and `||` parenthesis means (shell-ly) grouping the commands, and since `( ... )` introduces a subshell we will use `{ ... }` instead:
|
||||||
and since `( ... )` introduces a subshell we will use `{ ... }` instead:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
$ if [ "true" ] || { [ -e /does/not/exist ] && [ -e /does/not/exist ] ;} ; then echo true; else echo false; fi
|
$ if [ "true" ] || { [ -e /does/not/exist ] && [ -e /does/not/exist ] ;} ; then echo true; else echo false; fi
|
||||||
true
|
true
|
||||||
|
```
|
||||||
|
|
||||||
For the test command, the precedence parenthesis are, as well, `( )`,
|
For the test command, the precedence parenthesis are, as well, `( )`, but you need to escape or quote them, so that the shell doesn't try to interpret them:
|
||||||
but you need to escape or quote them, so that the shell doesn\'t try to
|
|
||||||
interpret them:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
$ if [ \( "true" -o -e /does/not/exist \) -a -e /does/not/exist ]; then echo true; else echo false; fi
|
$ if [ \( "true" -o -e /does/not/exist \) -a -e /does/not/exist ]; then echo true; else echo false; fi
|
||||||
false
|
false
|
||||||
|
|
||||||
# equivalent, but less readable IMHO:
|
# equivalent, but less readable IMHO:
|
||||||
$ if [ '(' "true" -o -e /does/not/exist ')' -a -e /does/not/exist ]; then echo true; else echo false; fi
|
$ if [ '(' "true" -o -e /does/not/exist ')' -a -e /does/not/exist ]; then echo true; else echo false; fi
|
||||||
false
|
false
|
||||||
|
```
|
||||||
|
|
||||||
## NOT
|
## NOT
|
||||||
|
|
||||||
As for AND and OR, there are 2 ways to negate a test with the shell
|
As for AND and OR, there are 2 ways to negate a test with the shell keyword `!` or passing `!` as an argument to `test`.
|
||||||
keyword `!` or passing `!` as an argument to `test`.
|
|
||||||
|
|
||||||
Here `!` negates the exit status of the command `test` which is 0
|
Here `!` negates the exit status of the command `test` which is 0 (true), and the else part is executed:
|
||||||
(true), and the else part is executed:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
if ! [ -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
if ! [ -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
||||||
|
```
|
||||||
|
|
||||||
Here the `test` command itself exits with status 1 (false) and the else
|
Here the `test` command itself exits with status 1 (false) and the else is also executed:
|
||||||
is also executed:
|
|
||||||
|
|
||||||
|
```plain
|
||||||
if [ ! -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
if [ ! -d '/tmp' ]; then echo "/tmp doesn't exists"; else echo "/tmp exists"; fi
|
||||||
|
```
|
||||||
|
|
||||||
Unlike for AND and OR, both methods for NOT have an identical behaviour,
|
Unlike for AND and OR, both methods for NOT have an identical behaviour, at least for doing one single test.
|
||||||
at least for doing one single test.
|
|
||||||
|
|
||||||
## Pitfalls summarized
|
## Pitfalls summarized
|
||||||
|
|
||||||
In this section you will get all the mentioned (and maybe more) possible
|
In this section you will get all the mentioned (and maybe more) possible pitfalls and problems in a summary.
|
||||||
pitfalls and problems in a summary.
|
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
||||||
Here\'s the copy of a mail on bug-bash list. A user asking a question
|
Here's the copy of a mail on bug-bash list.
|
||||||
about using the test command in Bash, **he\'s talking about a problem,
|
A user asking a question about using the test command in Bash, **he's talking about a problem, which you may have already had yourself**:
|
||||||
which you may have already had yourself**:
|
|
||||||
|
|
||||||
|
```plain
|
||||||
From: (PROTECTED)
|
From: (PROTECTED)
|
||||||
Subject: -d option not working. . .?
|
Subject: -d option not working. . .?
|
||||||
Date: Tue, 11 Sep 2007 21:51:59 -0400
|
Date: Tue, 11 Sep 2007 21:51:59 -0400
|
||||||
@ -389,20 +354,20 @@ which you may have already had yourself**:
|
|||||||
}
|
}
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Regards
|
Regards
|
||||||
|
```
|
||||||
|
|
||||||
See the problem regarding the used test-command (the other potential
|
See the problem regarding the used test-command (the other potential problems are not of interest here)?
|
||||||
problems are not of interest here)?
|
|
||||||
|
|
||||||
|
```bash
|
||||||
[-d $i]
|
[-d $i]
|
||||||
|
```
|
||||||
|
|
||||||
He simply didn\'t know that `test` or `[` is a normal, simple command.
|
He simply didn't know that `test` or `[` is a normal, simple command.
|
||||||
Well, here\'s the answer he got. I quote it here, because it\'s a well
|
Well, here's the answer he got.
|
||||||
written text that addresses most of the common issues with the
|
I quote it here, because it's a well written text that addresses most of the common issues with the "classic" test command:
|
||||||
\"classic\" test command:
|
|
||||||
|
|
||||||
|
```plain
|
||||||
From: Bob Proulx (EMAIL PROTECTED)
|
From: Bob Proulx (EMAIL PROTECTED)
|
||||||
Subject: Re: -d option not working. . .?
|
Subject: Re: -d option not working. . .?
|
||||||
Date: Wed, 12 Sep 2007 10:32:35 -0600
|
Date: Wed, 12 Sep 2007 10:32:35 -0600
|
||||||
@ -522,12 +487,12 @@ written text that addresses most of the common issues with the
|
|||||||
useful as a gentle introduction and interesting anyway.
|
useful as a gentle introduction and interesting anyway.
|
||||||
|
|
||||||
Bob
|
Bob
|
||||||
|
```
|
||||||
|
|
||||||
I hope this text protects you a bit from stepping from one pitfall into
|
I hope this text protects you a bit from stepping from one pitfall into the next.
|
||||||
the next.
|
|
||||||
|
|
||||||
I find it very interesting and informative, that\'s why I quoted it
|
I find it very interesting and informative, that's why I quoted it here.
|
||||||
here. Many thanks, Bob, also for the permission to copy the text here!
|
Many thanks, Bob, also for the permission to copy the text here!
|
||||||
|
|
||||||
## Code examples
|
## Code examples
|
||||||
|
|
||||||
@ -538,37 +503,31 @@ Some code snipplets follow, different ways of shell reaction is used.
|
|||||||
- **check if a variable is defined/non-NULL**
|
- **check if a variable is defined/non-NULL**
|
||||||
- `test "$MYVAR"`
|
- `test "$MYVAR"`
|
||||||
- `[ "$MYVAR" ]`
|
- `[ "$MYVAR" ]`
|
||||||
- **Note:** There are possibilities to make a difference if a
|
- **Note:** There are possibilities to make a difference if a variable is *undefined* or *NULL* - see [Parameter Expansion - Using an alternate value](/syntax/pe#use_an_alternate_value)
|
||||||
variable is *undefined* or *NULL* - see [Parameter Expansion -
|
|
||||||
Using an alternate value](/syntax/pe#use_an_alternate_value)
|
|
||||||
- **check if a directory exists, if not, create it**
|
- **check if a directory exists, if not, create it**
|
||||||
- `test ! -d /home/user/foo && mkdir /home/user/foo`
|
- `test ! -d /home/user/foo && mkdir /home/user/foo`
|
||||||
- `[ ! -d /home/user/foo ] && mkdir /home/user/foo`
|
- `[ ! -d /home/user/foo ] && mkdir /home/user/foo`
|
||||||
- `if [ ! -d /home/user/foo ]; then mkdir /home/user/foo; fi`
|
- `if [ ! -d /home/user/foo ]; then mkdir /home/user/foo; fi`
|
||||||
- **check if minimum one parameter was given, and that one is
|
- **check if minimum one parameter was given, and that one is "Hello"**
|
||||||
\"Hello\"**
|
|
||||||
- `test $# -ge 1 -a "$1" = "Hello" || exit 1`
|
- `test $# -ge 1 -a "$1" = "Hello" || exit 1`
|
||||||
- `[ $# -ge 1 ] && [ "$1" = "Hello" ] || exit 1` (see [lists
|
- `[ $# -ge 1 ] && [ "$1" = "Hello" ] || exit 1` (see [lists description](/syntax/basicgrammar#lists))
|
||||||
description](/syntax/basicgrammar#lists))
|
|
||||||
|
|
||||||
### Listing directories
|
### Listing directories
|
||||||
|
|
||||||
Using a [for-loop](/syntax/ccmd/classic_for) to iterate through all
|
Using a [for-loop](/syntax/ccmd/classic_for) to iterate through all entries of a directory, if an entry is a directory (`[ -d "$fn" ]`), print its name:
|
||||||
entries of a directory, if an entry is a directory (`[ -d "$fn" ]`),
|
|
||||||
print its name:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
for fn in *; do
|
for fn in *; do
|
||||||
[ -d "$fn" ] && echo "$fn"
|
[ -d "$fn" ] && echo "$fn"
|
||||||
done
|
done
|
||||||
|
```
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- Internal: [conditional
|
- Internal: [conditional expression](/syntax/ccmd/conditional_expression) (aka "the new test command")
|
||||||
expression](/syntax/ccmd/conditional_expression) (aka \"the new test
|
|
||||||
command\")
|
|
||||||
- Internal: [the if-clause](/syntax/ccmd/if_clause)
|
- Internal: [the if-clause](/syntax/ccmd/if_clause)
|
||||||
|
|
||||||
[^1]: \<rant\>Of course, one can wonder what is the use of including the
|
[^1]: <rant\>Of course, one can wonder what is the use of including the
|
||||||
parenthesis in the specification without defining the behaviour with
|
parenthesis in the specification without defining the behaviour with
|
||||||
more than 4 arguments or how usefull are the examples with 7 or 9
|
more than 4 arguments or how usefull are the examples with 7 or 9
|
||||||
arguments attached to the specification.\</rant\>
|
arguments attached to the specification.</rant\>
|
||||||
|
11
mkdocs.yml
11
mkdocs.yml
@ -38,6 +38,17 @@ plugins:
|
|||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- admonition
|
- admonition
|
||||||
|
- pymdownx.emoji:
|
||||||
|
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||||
|
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||||
|
- pymdownx.highlight:
|
||||||
|
anchor_linenums: true
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.highlight
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.keys
|
||||||
|
- pymdownx.smartsymbols
|
||||||
|
- footnotes
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- Start: index.md
|
- Start: index.md
|
||||||
|
Loading…
Reference in New Issue
Block a user