Getting started with Bash scripts

A script is a text file that contains a series of commands. Shell reads this file and executes all the commands in it in turn, as if these commands were directly input to the command line. All tasks that can be done on the command line can be done with scripts.

The advantage of scripts is that they can be reused, or they can be automatically called on specific occasions, such as automatic execution of scripts when the system is started or shut down.

Shebang line

The first line of the script is usually to specify the interpreter, that is, the interpreter through which the script must be executed. This line starts with the character #!, this character is called Shebang, so this line is called the Shebang line.

After #! is the location of the script interpreter. The interpreter for Bash scripts is generally /bin/sh or /bin/bash.

#!/bin/sh
# Or
#!/bin/bash

There is no space between #! and the script interpreter.

If the Bash interpreter is not placed in the directory /bin, the script cannot be executed. For insurance, it can be written as follows.

#!/usr/bin/env bash

The above command uses the env command (this command is always in the /usr/bin directory) to return the location of the Bash executable file. For a detailed introduction to the env command, please see the following text.

The Shebang line is not required, but it is recommended to add it. If this line is missing, you need to manually pass the script to the interpreter. For example, the script is script.sh, when there is a Shebang line, it can be called and executed directly.

$ ./script.sh

In the above example, script.sh is the name of the script file. Scripts usually use the .sh extension, but this is not required.

If there is no Shebang line, you can only manually pass the script to the interpreter for execution.

$ /bin/sh ./script.sh
# Or
$ bash ./script.sh

Execution permissions and paths

As mentioned earlier, as long as the Shebang line script is specified, it can be executed directly. There is a prerequisite for this, that is, the script needs to have execution permissions. You can use the following command to grant script execution permissions.

# Give all users execute permissions
$ chmod +x script.sh

# Give all users read and execute permissions
$ chmod +rx script.sh
# Or
$ chmod 755 script.sh

# Only give the script owner read and execute permissions
$ chmod u+rx script.sh

Script permissions are usually set to 755 (the owner has all permissions, others have read and execute permissions) or 700 (only the owner can execute).

In addition to the execution permission, when the script is called, it is generally necessary to specify the path of the script (such as path/script.sh). If the script is placed in the directory specified by the environment variable $PATH, there is no need to specify the path. Because Bash will automatically go to these directories to find if there is an executable file with the same name.

It is recommended to create a subdirectory of ~/bin in the main directory to store executable scripts, and then add ~/bin to $PATH.

export PATH=$PATH:~/bin

The above command changes the environment variable $PATH and adds ~/bin to the end of $PATH. You can add this line to the ~/.bashrc file, and then reload the .bashrc once, and this configuration will take effect.

$ source ~/.bashrc

In the future, no matter what directory you are in, directly enter the script file name and the script will be executed.

$ script.sh

The above command does not specify the script path because script.sh is in the directory specified by $PATH.

env command

The env command always points to the /usr/bin/env file, or in other words, the binary file is always in the directory /usr/bin.

The syntax of #!/usr/bin/env NAME means to let the Shell find the first matching NAME in the $PATH environment variable. If you don't know the specific path of a certain command, or want to be compatible with other users' machines, this way of writing is useful.

/usr/bin/env bash means to return the location of the executable file of bash, provided that the path of bash is in $PATH. Other script files can also use this command. For example, the Shebang line of the Node.js script can be written as follows.

#!/usr/bin/env node

The parameters of the env command are as follows.

  • -i, --ignore-environment: Start without environment variables.
  • -u, --unset=NAME: Delete a variable from the environment variable.
  • --help: Display help.
  • --version: Output version information.

The following is an example of creating a new Shell without any environment variables.

$ env -i /bin/sh

Comment

In Bash script, # means comment, which can be placed at the beginning or end of the line.

# This line is a comment
echo'Hello World!'

echo'Hello World!' # The part after the pound sign is also a comment

It is recommended to use comments at the beginning of the script to explain the role of the current script, which is beneficial to future maintenance.

Script parameters

When calling a script, there can be parameters after the script file name.

$ script.sh word1 word2 word3

In the above example, script.sh is a script file, and word1, word2 and word3 are three parameters.

Inside the script file, you can use special variables to reference these parameters.

  • $0: script file name, namely script.sh.
  • $1~$9: Corresponding to the first parameter to the ninth parameter of the script.
  • $#: The total number of parameters.
  • $@: All parameters, separated by spaces.
  • $*: All parameters, separated by the first character of the variable $IFS value between the parameters, the default is a space, but it can be customized.

If the script has more than 9 parameters, the 10th parameter can be quoted in the form of ${10}, and so on.

Note that if the command is command -o foo bar, then -o is $1, foo is $2, and bar is $3.

The following is an example of reading command line parameters inside a script.

#!/bin/bash
# script.sh

echo "All parameters:" $@
echo "Number of command line parameters:" $#
echo'$0 = '$0
echo'$1 = '$1
echo'$2 = '$2
echo'$3 = '$3

The results of the implementation are as follows.

$ ./script.sh abc
All parameters: abc
Number of command line parameters: 3
$0 = script.sh
$1 = a
$2 = b
$3 = c

The user can input any number of parameters, and each parameter can be read using the for loop.

#!/bin/bash

for i in "$@"; do
  echo $i
done

In the above example, $@ returns a list of all parameters, and then uses for to loop through it.

If multiple parameters are placed in double quotes, they are treated as one parameter.

$ ./script.sh "a b"

In the above example, Bash would think that "a b" is a parameter, and $1 would return ab. Note that the double quotes are not included when returning.

shift command

The shift command can change the script parameters. Each time it is executed, the current first parameter ($1) of the script will be removed, making the following parameters one digit forward, that is, $2 becomes $1, $3 Becomes $2, $4 becomes $3, and so on.

The while loop combined with the shift command can also read every parameter.

#!/bin/bash

echo "A total of $# parameters have been entered"

while ["$1" != "" ]; do
  echo "$# parameters left"
  echo "Parameter: $1"
  shift
done

In the above example, the shift command removes the current first parameter each time, so as to traverse all the parameters through the while loop.

The shift command can accept an integer as a parameter, specify the number of parameters to be removed, the default is 1.

shift 3

The above command removes the first three parameters, and the original $4 becomes $1.

getopts command

The getopts command is used inside the script and can parse complex script command line parameters. It is usually used with the while loop to take out all the parameters of the script with the pre-conjunction line (-).

getopts optstring name

It takes two parameters. The first parameter optstring is a string, giving all the hyphenation line parameters of the script. For example, a script can have three configuration item parameters -l, -h, and -a, of which only -a can carry parameter values, and -l and -h are switches Parameter, then the first parameter of getopts is written as lha:, the order is not important. Note that there is a colon after a, which means that the parameter has a parameter value, and getopts specifies the configuration item parameter with parameter value, which must be followed by a colon (:). The second parameter name of getopts is a variable name, which is used to save the currently retrieved configuration item parameters, namely l, h or a.

Below is an example.

while getopts'lha:' OPTION; do
  case "$OPTION" in
    l)
      echo "linuxconfig"
      ;;

    h)
      echo "h stands for h"
      ;;

    a)
      avalue="$OPTARG"
      echo "The value provided is $OPTARG"
      ;;
    ?)
      echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2
      exit 1
      ;;
  esac
done
shift "$(($OPTIND-1))"

In the above example, the while loop continues to execute the getopts'lha:' OPTION command, and each time it is executed, it reads a conjunction line parameter (and the corresponding parameter value), and then enters the loop body. The variable OPTION saves the connection line parameter currently being processed (ie l, h or a). If the user enters an unspecified parameter (such as -x), then OPTION is equal to ?. Use the case judgment in the loop body to handle these four different situations.

If a certain conjunction line parameter has a parameter value, such as -a foo, when processing the a parameter, the environment variable $OPTARG saves the parameter value.

Note that as long as it encounters a parameter without a conjunction line, getopts will fail to execute, thus exiting the while loop. For example, getopts can parse command -l foo but not command foo -l. In addition, the form in which multiple conjunction line parameters are written together, such as command -lh and getopts can also be handled correctly.

The variable $OPTIND is 1 before the start of getopts execution, and then it is increased by 1 every time it is executed. Waiting until you exit the while loop, it means that all the conjunction line parameters have been processed. At this time, $OPTIND-1 is the number of conjunctive line parameters that have been processed. Use the shift command to remove these parameters to ensure that the following code can use the main parameters of the command such as $1 and $2.

Configuration item parameter terminator --

When a variable is used as a command parameter, sometimes it is hoped that the specified variable can only be used as an entity parameter, not as a configuration item parameter. In this case, the configuration item parameter terminator -- can be used.

$ myPath="~/docs"
$ ls - $myPath

In the above example, the -- mandatory variable $myPath can only be interpreted as an entity parameter (namely path name).

If the variable is not a path name, an error will be reported.

$ myPath="-l"
$ ls - $myPath
ls: Cannot access'-l': No such file or directory

In the above example, the value of the variable myPath is -l, not a path. However, --mandatory $myPath can only be interpreted as a path, resulting in an error "The path does not exist".

The main function of the parameter terminator is that if the parameter at the beginning of the dash is to be used as an entity parameter, it needs to be used.

$ grep - "--hello" example.txt

The above command is in the example.txt file, search for the string --hello. This string starts with a dash. If the parameter terminator is not used, the grep command will use --hello as a configuration item parameter and report an error.

exit command

The exit command is used to terminate the execution of the current script and return an exit value to the Shell.

$ exit

The above command aborts the current script, and uses the exit status of the last command as the exit status of the entire script.

The exit command can be followed by a parameter, which is the exit status.

# Exit value is 0 (success)
$ exit 0

# The exit value is 1 (failure)
$ exit 1

When exiting, the script will return an exit value. The exit value of the script, 0 means normal, 1 means an error occurred, 2 means the usage is incorrect, 126 means it is not an executable script, and 127 means the command is not found. If the script is terminated by the signal N, the exit value is 128 + N. Simply put, as long as the exit value is not 0, it is considered an execution error.

Below is an example.

if [$(id -u) != "0" ]; then
  echo "The root user can execute the current script"
  exit 1
fi

In the above example, the id -u command returns the user's ID. Once the user's ID is not equal to 0 (the root user's ID), the script will exit with an exit code of 1, indicating a failure.

The difference between exit and return commands is that the return command exits the function and returns a value to the caller, the script still executes. Exit is the exit of the entire script. If exit is called in a function, the function will exit and the script execution will be terminated.

Command execution result

After the command is executed, there will be a return value. 0 means successful execution, non-0 (usually 1) means execution failed. The environment variable $? can read the return value of the previous command.

Using this, you can judge the execution result of the command in the script.

cd $some_directory
if ["$?" = "0" ]; then
  rm *
else
  echo "Cannot switch directory!" 1>&2
  exit 1
fi

In the above example, if the command cd $some_directory is executed successfully (the return value is equal to 0), the files in the directory will be deleted, otherwise the script will exit, and the return value of the whole script will become 1, indicating that the execution failed.

Because if can directly judge the execution result of the command and perform the corresponding operation, the above script can be rewritten as follows.

if cd $some_directory; then
  rm *
else
  echo "Could not change directory! Aborting." 1>&2
  exit 1
fi

A more concise way is to use two logical operators && (and) and || (or).

# The first step is executed successfully, the second step will be executed
cd $some_directory && rm *

# If the first step fails, the second step will be executed
cd $some_directory || exit 1

source command

The source command is used to execute a script, usually used to reload a configuration file.

$ source .bashrc

The biggest feature of the source command is that the script is executed in the current shell, unlike when the script is executed directly, a new sub-Shell is created. Therefore, when the source command executes the script, the export variable is not needed.

#!/bin/bash
# test.sh
echo $foo

The above script outputs the value of the $foo variable.

# The current Shell creates a new variable foo
$ foo=1

# Print output 1
$ source test.sh
1

# Print out an empty string
$ bash test.sh

In the above example, the current shell variable foo has no export, so it cannot be read directly by executing it, but it can be read by executing source.

Another use of the source command is to load external libraries inside the script.

#!/bin/bash

source ./lib.sh

function_from_lib

The above script uses the source command internally to load an external library, and then you can use the functions defined by this external library in the script.

There is a shorthand for source, which can be represented by a dot (.).

$. .bashrc

Alias, alias command

The alias command is used to assign an alias to a command, which is easier to remember. The following is the format of alias.

alias NAME=DEFINITION

In the above command, NAME is the name of the alias, and DEFINITION is the original command corresponding to the alias. Note that there can be no spaces on both sides of the equal sign, otherwise an error will be reported.

A common example is to give the grep command an alias of search.

alias search=grep

alias can also be used to specify a shorter alias for long commands. The following is a command to define a today through an alias.

$ alias today='date +"%A, %B %-d, %Y"'
$ today
Monday, January 6, 2020

Sometimes in order to prevent accidental deletion of files, you can specify the alias of the rm command.

$ alias rm='rm -i'

The above command specifies that the rm command is rm -i, and the user will be asked to confirm each time before deleting a file.

The alias defined by alias can also accept parameters, which will be passed directly to the original command.

$ alias echo='echo It says: '
$ echo hello world
It says: hello world

In the above example, the alias defines the first two parameters of the echo command, which is equivalent to modifying the default behavior of the echo command.

After specifying the alias, you can use the alias like other commands. Generally speaking, commonly used aliases are written at the end of ~/.bashrc. In addition, you can only define aliases for commands. It is invalid to define aliases for other parts (such as very long paths).

Call the alias command directly to display all aliases.

$ alias

The unalias command can unalias.

$ unalias lt