set command, shopt command

set command is an important part of Bash script, but it is often overlooked, causing problems with the security and maintainability of the script. This chapter introduces the basic usage of set to help you write safer Bash scripts.

Introduction

We know that when Bash executes a script, it creates a child shell.

$ bash script.sh
```In the

above code, `script.sh` is executed in a sub-Shell. This sub-Shell is the execution environment of the script, and Bash gives various parameters of this environment by default.

The `set` command is used to modify the operating parameters of the sub-Shell environment, that is, the customized environment. A total of more than a dozen parameters can be customized. [Official Manual](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html) has a complete list. This chapter introduces the most commonly used ones. several.

By the way, if you run `set` directly without any parameters on the command line, all environment variables and Shell functions will be displayed.

```bash
$ set

set -u When

executing a script, if a variable that does not exist is encountered, Bash ignores it by default.

#!/usr/bin/env bash

echo $a
echo bar
```In the

above code, `$a` is a non-existent variable. The results of the implementation are as follows.

```bash
$ bash script.sh

bar

You can see that echo $a outputs a blank line, Bash ignores the non-existent $a, and then continues to execute echo bar. In most cases, this is not the behavior that the developer wants. When the variable does not exist, the script should report an error instead of executing it silently.

set -u is used to change this behavior. The script adds it to the head, it will report an error when encountering a variable that does not exist, and stop execution.

#!/usr/bin/env bash
set -u

echo $a
echo bar
```The

running results are as follows.

`` bash '
$ script.sh bash
bash: script.sh: Line 4: a: unbound variable
`` `

you can see, the script error, and no longer perform the statements that follow.

There is another way to write `-u`, `-o nounset`, the two are equivalent.

```bash
set -o nounset

set -x

By default, after the script is executed, only the running result is output, nothing else. If multiple commands are executed continuously, their results will be output continuously. Sometimes it is not clear what command produced a certain piece of content.

set -x is used to output the executed line of commands before running the results.

#!/usr/bin/env bash
set -x

echo bar

Execute the above script, the results are as follows.

$ bash script.sh
+ echo bar
bar
```It

can be seen that before executing `echo bar`, the command will be printed out first, and the beginning of the line is indicated by `+`. This is useful for debugging complex scripts.

There is another way to write `-x`, `-o xtrace`.
If you want to turn off the command output in the

```bash
set -o xtrace

script, you can use set +x.

#!/bin/bash

number=1

set -x
if [$number = "1" ]; then
  echo "Number equals 1"
else
  echo "Number does not equal 1"
fi
set +x

above In the example, the command output is only turned on for a specific code segment.

Bash's error handling

If there is a failed command in the script (the return value is not 0), Bash will continue to execute the following command by default.

#!/usr/bin/env bash

foo
echo bar
```In the

above script, `foo` is a non-existent command, and an error will be reported when executed. However, Bash will ignore this error and continue execution.

```bash
$ bash script.sh
script.sh: line 3: foo: command
bar not found
``` As

you can see, Bash just shows an error, and does not terminate the execution.

This behavior is not conducive to script security and debugging. In actual development, if a command fails, the script often needs to stop execution to prevent errors from accumulating. At this time, generally use the following wording.

```bash
command || exit 1
```The

above writing means that as long as `command` has a non-zero return value, the script will stop executing.

If multiple operations need to be completed before stopping execution, the following three writing methods should be used.

```bash
# Writing a
command || {echo "command failed"; exit 1;}

# Writing two
if! command; then echo "command failed"; exit 1; fi

# Writing three
command
if ["$?" -ne 0 ]; then echo "command failed"; exit 1; fi

In addition, there is another situation besides stopping execution. If the two commands have an inheritance relationship, and only the first command succeeds, the second command can continue to be executed, then the following way of writing should be used.

command1 && command2

set -e The

above writing is somewhat troublesome and easy to neglect. set -e fundamentally solves this problem, it makes the script terminate execution whenever an error occurs.

#!/usr/bin/env bash
set -e

foo
echo bar
```The

execution result is as follows.

```bash
$ bash script.sh
script.sh: Line 4: foo: Command not
found``` As

you can see, after the execution of line 4 fails, the script terminates execution.

`set -e` judges whether a command failed according to the return value. However, the non-zero return value of some commands may not indicate failure, or the developer hopes that the script will continue to execute if the command fails. At this time, you can temporarily close `set -e`, and then reopen `set -e` after the command is executed.

```bash
set +e
command1
command2
set -e
```In the

above code, `set +e` means to close the `-e` option, and `set -e` means to reopen the `-e` option.

Another way is to use `command || true` so that even if the command fails, the script will not terminate the execution.

```bash
#!/bin/bash
set -e

foo || true
echo bar
```In the

above code, `true` makes this line of statement always executed successfully, and the following `echo bar` will execute.

There is another way to write `-e`, `-o errexit`.

```bash
set -o errexit

set -o pipefail

set -e has one exception, which is not applicable to pipe commands.

The so-called pipeline command is that multiple sub-commands are combined into one big command through the pipeline operator (|). Bash will use the return value of the last subcommand as the return value of the entire command. In other words, as long as the last subcommand does not fail, the pipeline command will always be executed successfully, so the subsequent commands will still be executed, and set -e will be invalid.

Please see the example below.

#!/usr/bin/env bash
set -e

foo | echo a
echo bar
```The

execution result is as follows.

```bash
$ bash script.sh
a
script.sh: Line 4: foo: Command
bar not found
```In the

above code, `foo` is a non-existent command, but the pipeline command `foo | echo a` will execute successfully. As a result, the following `echo bar` will continue to execute.

`set -o pipefail` is used to solve this situation. As long as one subcommand fails, the entire pipeline command fails, and the script terminates its execution.

```bash
#!/usr/bin/env bash
set -eo pipefail

foo | echo a
echo bar
After running

, the results are as follows.

bash ' $ bash script.sh A script.sh: Line 4: foo: command not found `

you can see, echo bar not executed.

set -E

Once the -e parameter is set, errors in the function will not be caught by the trap command (refer to the chapter "trap command"). The -E parameter can correct this behavior so that the function can also inherit the trap command.

#!/bin/bash
set -e

trap "echo ERR trap fired!" ERR

myfunc()
{
  #'foo' is a non-existent command
  foo
}

myfunc
``` In the

above example, a non-existent command `foo` is called inside the `myfunc` function, which causes an error to be reported when the function is executed.

```bash
$ bash test.sh
test.sh: line 9: foo: command not found

However, due to the setting of set -e, the error inside the function is not captured by the trap command, so you need to add Only use the -E parameter.

#!/bin/bash
set -Eeuo pipefail

trap "echo ERR trap fired!" ERR

myfunc()
{
  #'foo' is a non-existent command
  foo
}

myfunc

Execute the above script, you can See that the trap command is in effect.

$ bash test.sh
test.sh: Line 9: foo: Command not found
ERR trap fired!

Other parameters The

set command has some other parameters.

-set -n: equivalent to set -o noexec, do not run the command, only check whether the syntax is correct. -set -f: equivalent to set -o noglob, which means that wildcards are not used for file name expansion. -set -v: equivalent to set -o verbose, which means to print each line of input received by the Shell. -set -o noclobber: Prevent the use of the redirection operator > to overwrite existing files.

The above -f and -v parameters can be turned off with set +f and set +v respectively.

set command summary

Several parameters of the set command highlighted above are generally used together.

# Writing method one
set -Eeuxo pipefail

# Writing method two
set -Eeux
set -o pipefail

These two writing methods are recommended to be placed at the head of all Bash scripts.

Another way is to pass in these parameters from the command line when executing the Bash script.

$ bash -euxo pipefail script.sh

shopt command The

shopt command is used to adjust the parameters of the Shell, which is similar to the function of the set command. The main reason for these two similar commands is that set is inherited from Ksh and is part of the POSIX specification, while shopt is unique to Bash.

You can directly enter shopt to view all the parameters and their respective open and closed status.

$ shopt

shopt command is followed by the parameter name, you can check whether the parameter is turned on.

$ shopt globstar
globstar off
```The

above example indicates that the `globstar` parameter is off by default.

**(1)-s**

`-s` is used to open a certain parameter.

```bash
$ shopt -s optionNameHere

(2)-u

-u is used to turn off a parameter.

$ shopt -u optionNameHere
```For

example, the parameter `histappend` means to append the operation history to the history file when exiting the current Shell. This parameter is turned on by default. If you use the following command to turn it off, the operation history of the current Shell will replace the entire history file.

```bash
$ shopt -u histappend

(3)-q

The function of -q is also to query whether a parameter is turned on, but not directly output the query results, but through the execution status of the command ( $?) indicates the query result. If the status is 0, it means that the parameter is on; if it is 1, it means that the parameter is off.

$ shopt -q globstar
$ echo $?
1
```The

above command queries whether the `globstar` parameter is turned on. The return status is `1`, indicating that the parameter is closed.

This usage is mainly used in scripts for `if` conditional structures. The following example is to execute the statement inside the `if` structure if this parameter is turned on.

```bash
if !(shopt -q globstar); then
  ...
fi