Shell Scripts 1

LHT

Shell Overview

Shell is both a powerful command-line interface and a script language interpreter.

The Shell parsers provided by Linux are:

1
2
3
4
5
[root@lht ~]# cat /etc/shells 
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash

The relationship between bash and sh:

1
2
3
4
5
[root@lht ~]# cd /bin; ll | grep bash
-rwxr-xr-x. 1 root root 964608 Oct 31 2018 bash
lrwxrwxrwx. 1 root root 10 Jul 11 2019 bashbug -> bashbug-64
-rwxr-xr-x. 1 root root 6964 Oct 31 2018 bashbug-64
lrwxrwxrwx. 1 root root 4 Jul 11 2019 sh -> bash

sh is actually a symbolic link pointing to bash.

The default parser for CentOS is bash:

1
2
[root@lht bin]# echo $SHELL
/bin/bash

Shell Script Introduction

Script Format

Shell scripts must specify the parser on the first line, typically starting with #!/bin/bash.

The First Shell Script

Create a directory to store scripts in the home directory:

1
2
3
4
[root@lht ~]# mkdir scripts
[root@lht ~]# ls
scripts
[root@lht ~]# cd scripts/

Create the script file:

1
[root@lht scripts]# vim hello_world.sh

In fact, the file extension is not important in Linux, the extension here is just for identification.

Enter the following content in the script file:

1
2
#!/bin/bash
echo "hello world"

Executing the Script

  1. Use the bash or sh command with the script’s relative or absolute path to run the script (no need to grant execute permission):

    1
    2
    [root@lht scripts]# bash hello_world.sh 
    hello world

    Actually, this method is just for understanding, since we’ve already specified the script parser in the script file. Using the command to run the script is redundant. The other methods will not be repeated here.

  2. Execute the script by entering the absolute or relative path (must have executable permission).

    • Check the script file’s status.

    • Grant execute permission.

    • Check the system environment.
      Most Linux distributions add the user’s home directory’s bin directory to the PATH variable. In this case, the corresponding /root/bin directory is used. If this directory doesn’t exist, create it and place the script file inside.
      If we want to add our directory to the PATH variable, execute the following command:

      1
      [root@lht ~]# export PATH=$PATH:/root/scripts

      “/root/scripts” is the directory’s absolute path

    1
    2
    [root@lht scripts]# ll hello_world.sh 
    -rw-r--r-- 1 root root 31 Jun 23 23:50 hello_world.sh

    As we can see, the script file currently doesn’t have execute permission. Executing it directly via the path will cause an error.

    1
    2
    3
    [root@lht scripts]# chmod +x hello_world.sh 
    [root@lht scripts]# ll hello_world.sh
    -rwxr-xr-x 1 root root 31 Jun 23 23:50 hello_world.sh

    Execute the script using a relative path:

    1
    2
    [root@lht scripts]# ./hello_world.sh 
    hello world

    Note: ./ cannot be omitted. If omitted, the system will think you’re typing a command and will search for it in the environment variable, which of course will result in a “command not found” error.

    1
    2
    [root@lht ~]# echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

    Only commands in these directories can be executed directly from the command line.

    1
    2
    3
    [root@lht ~]# export PATH=$PATH:/root/scripts
    [root@lht ~]# echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/scripts

    This environment variable change is valid only for the current session.

    1
    [root@lht scripts]# vim /etc/profile

    Add the following at the end:

    1
    export PATH=$PATH:/root/scripts

    After modifying, you need to refresh the environment variable:

    1
    [root@lht scripts]# source /etc/profile

    Check the environment variable again:

    1
    2
    [root@lht scripts]# echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/scripts:/root/bin

    The default environment variable is:

    1
    export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

    Alternatively, you can add it to the user’s configuration file by adding the above statement to the ~/.bashrc file.

  3. Use source or . to execute the script.
    The usage is as follows:
    This method mainly differs from the first one in that using bash or sh to run a script opens a child shell by default, where the script is executed. Meanwhile, the source and . commands run the script in the current shell.
    The difference lies in the environment variable inheritance. Variables set in the child shell are not visible in the parent shell, and changes to environment variables in the child shell only affect the child shell.
    Since bash command is not commonly used to run scripts, this method is typically used for understanding purposes. The second method is generally preferred.

    1
    source script_file_path

    Or:

    1
    . script_file_path

    This allows the script’s commands to be executed in the current shell environment, making the environment variables defined in the script effective in the current shell.

Variables

System-Defined Variables

Common system variables:

$HOME represents the path of the current user’s home directory.
$PWD represents the path of the current working directory.
$SHELL represents the path of the current shell being used.
$USER represents the current logged-in username.

View the value of system variables

1
2
[root@lht scripts]# echo $HOME
/root

Display all variables in the current shell

1
2
3
[root@lht scripts]# set
BASH=/bin/bash
...

Custom Variables

  1. Basic Syntax

    • Define variable: variable_name=variable_value, note that there should be no spaces around the = sign.
    • Unset variable: unset variable_name
    • Declare a read-only variable: readonly variable, note: it cannot be unset.
      If you want to disable a read-only variable, you need to restart the session.
  2. Variable Definition Rules

    • Variable names can consist of letters, numbers, and underscores, but cannot start with a number. It is recommended that environment variable names be uppercase.
    • No spaces around the equals sign.
    • In Bash, variables are of string type by default and cannot directly perform numeric operations.
    • If a variable’s value contains spaces, it needs to be enclosed in double quotes or single quotes.
  3. Practical Examples

    Regular variable

    1
    2
    3
    4
    5
    6
    [root@lht ~]# A=5
    [root@lht ~]# echo $A
    5
    [root@lht ~]# A=6
    [root@lht ~]# echo $A
    6

    Read-only variable, cannot be modified or unset

    1
    2
    3
    4
    5
    6
    7
    [root@lht ~]# readonly B=2
    [root@lht ~]# echo $B
    2
    [root@lht ~]# B=3
    -bash: B: readonly variable
    [root@lht ~]# unset B
    -bash: unset: B: cannot unset: readonly variable

    Arithmetic requires using $[] or $(())

    1
    2
    3
    4
    5
    6
    [root@lht ~]# C=1+2
    [root@lht ~]# echo $C
    1+2
    [root@lht ~]# C=$[1+2]
    [root@lht ~]# echo $C
    3

    Values with spaces need to be enclosed in double quotes or single quotes

    1
    2
    3
    4
    5
    [root@lht ~]# D=a space
    -bash: space: command not found
    [root@lht ~]# D="a space"
    [root@lht ~]# echo $D
    a space
  4. Promote a variable to a global variable

    Syntax

    1
    export variable_name

    Modify the previous script file

    1
    2
    3
    #!/bin/bash
    echo "hello world"
    echo $B
    1
    2
    3
    4
    5
    6
    7
    [root@lht scripts]# hello_world.sh 
    hello world

    [root@lht scripts]# export B
    [root@lht scripts]# hello_world.sh
    hello world
    2

Special Variables

  1. $n

    $n (where n is a number) represents parameter variables in a script. Specifically:

    • $0 represents the name of the current script.
    • $1 to $9 represent the first to ninth arguments of the script.
    • For arguments greater than ten, curly braces are needed, for example, ${10} represents the tenth argument, ${11} represents the eleventh argument, and so on.
      These parameter variables are used to get the values passed to the script.

    Example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@lht scripts]# vim parameter.sh
    #!/bin/bash
    echo $0
    echo $1
    echo $2
    [root@lht scripts]# chmod +x parameter.sh
    [root@lht scripts]# parameter.sh
    /root/scripts/parameter.sh

    [root@lht scripts]# parameter.sh a b
    /root/scripts/parameter.sh
    a
    b
    [root@lht scripts]#
  2. $#

    $# is a special parameter variable used to get the number of input parameters. It is often used in loops, to check if the number of parameters is correct, and to improve the robustness of the script.

    We can use this feature to write scripts that perform different operations based on the number of parameters. For example, if the number of parameters is not correct, an error message can be displayed, or the user can be reminded to use the script correctly.

    Example

    1
    2
    3
    4
    5
    if [ $# -ne 2 ]; then
    echo "The script requires two parameters!"
    echo "Usage: bash script_name param1 param2"
    exit 1
    fi

    This code checks if the number of parameters passed to the script is 2. If not, it displays an error message and exits the script. This improves the robustness of the script, ensuring that the correct number of parameters are passed.

    Example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [root@lht scripts]# vim parameter.sh
    #!/bin/bash
    echo $0
    echo $1
    echo $2
    echo "---------"
    echo $#
    [root@lht scripts]# parameter.sh a b c
    /root/scripts/parameter.sh
    a
    b
    ---------
    3
  3. $*, $@

    • $*: When enclosed in double quotes "", it treats all command-line arguments as a whole, returning them as a single string.
    • $@: When enclosed in double quotes "", it treats each command-line argument separately, returning each as an individual string.
      These variables are commonly used in loops to iterate over command-line arguments.

    Here’s an example showing how to use $* and $@ in a loop:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    [root@lht scripts]# vim parameter.sh
    #!/bin/bash
    echo $0
    echo $1
    echo $2
    echo "---------"
    echo $#
    # Using $* to loop through arguments
    echo "Using \$* to loop through arguments:"
    for arg in "$*"; do
    echo "$arg"
    done

    echo

    # Using $@ to loop through arguments
    echo "Using \$@ to loop through arguments:"
    for arg in "$@"; do
    echo "$arg"
    done
    [root@lht scripts]# parameter.sh a b c
    /root/scripts/parameter.sh
    a
    b
    ---------
    3
    Using $* to loop through arguments:
    a b c

    Using $@ to loop through arguments:
    a
    b
    c

    Here we mentioned loops that have not yet been explained. We will discuss them in detail later. For readers with some programming experience, understanding these commands should not be difficult.

  4. $?

    $?: The return status of the last executed command.

    If the value of this variable is 0, it indicates that the previous command was executed successfully.

    If the value is non-zero (the specific number is determined by the command itself), it indicates that the previous command failed.

    1
    2
    3
    4
    5
    [root@lht scripts]# hello_world.sh 
    hello world
    2
    [root@lht scripts]# echo $?
    0

Conditional Judgment

Syntax

  • test condition
  • [ condition ] (Note: There must be spaces before and after condition)

Note: A non-empty condition is true, [ content ] returns true, and [ ] returns false.

Common Judgment Conditions

  • Comparing two integers:
    • -eq equals (equal)
    • -ne not equal (not equal)
    • -lt less than (less than)
    • -le less than or equal (less equal)
    • -gt greater than (greater than)
    • -ge greater than or equal (greater equal)
  • Comparing strings:
    • Use = to check for equality.
    • Use != to check for inequality.
  • Checking file permissions:
    • -r has read permission (read)
    • -w has write permission (write)
    • -x has execute permission (execute)
  • Checking file types:
    • -e file exists (existence)
    • -f file exists and is a regular file (file)
    • -d file exists and is a directory (directory)

Example

1
2
3
4
5
6
7
8
9
10
11
12
[root@lht scripts]# [ 8 -gt 6 ]
[root@lht scripts]# echo $?
0
[root@lht scripts]# [ -x hello_world.sh ]
[root@lht scripts]# echo $?
0
[root@lht scripts]# [ -e /root/scripts/parameter.sh ]
[root@lht scripts]# echo $?
0
[root@lht scripts]# [ content ]
[root@lht scripts]# echo $?
0

Flow Control (Key)

if Statement

Syntax

  • Single branch

    1
    2
    3
    if [ condition ]; then
    program
    fi

    In one line of command, multiple commands can be entered, separated by semicolons. Thus, the above format is equivalent to:

    1
    2
    3
    4
    if [ condition ]
    then
    program
    fi
  • Multiple branches

    1
    2
    3
    4
    5
    6
    7
    if [ condition ]; then
    program
    elif [ condition ]; then
    program
    else
    program
    fi

Notes:

  • There must be spaces between [ condition ] and the condition expression.
  • There must be a space after if.

Example

1
[root@lht scripts]# vim if.sh 
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

if [ $1 -eq 1 ]
then
echo "number 1"
elif [ $1 -eq 2 ]
then
echo "number 2"
else
echo "not number 1 or 2"
fi
1
2
3
4
5
6
7
[root@lht scripts]# chmod +x if.sh 
[root@lht scripts]# if.sh 1
number 1
[root@lht scripts]# if.sh 2
number 2
[root@lht scripts]# if.sh 5
not number 1 or 2

case Statement

Syntax

1
2
3
4
5
6
7
8
9
10
11
12
case $variable in
"value 1")
If the value of the variable equals value 1, execute program 1
;;
"value 2")
If the value of the variable equals value 2, execute program 2
;;
…other branches omitted…
*)
If the value of the variable is not any of the above values, execute this program
;;
esac

Notes:

  • The case line must end with the word “in”, and each pattern matching must end with a closing parenthesis ).
  • Double semicolons ;; indicate the end of a command sequence, similar to a break in Java.
  • The last *) represents the default case, similar to default in Java.

Example

1
[root@lht scripts]# vim case.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

case $1 in
"1")
echo "number 1"
;;
"2")
echo "number 2"
;;
*)
echo "not number 1 or 2"
;;
esac
1
2
3
4
5
6
7
[root@lht scripts]# chmod +x case.sh 
[root@lht scripts]# case.sh 1
number 1
[root@lht scripts]# case.sh 2
number 2
[root@lht scripts]# case.sh 3
not number 1 or 2

for Loop

Syntax 1 (Regular for loop)

1
2
3
4
for (( initial value; loop control condition; variable change ))
do
program
done

Example

1
[root@lht scripts]# vim for.sh
1
2
3
4
5
6
7
8
#!/bin/bash

sum=0
for ((i = 0; i < 101; i++))
do
sum=$[ $sum + $i ]
done
echo $sum
1
2
3
[root@lht scripts]# chmod +x for.sh 
[root@lht scripts]# for.sh
5050

Note: The reason the loop condition can directly use < for comparison is because it is in double parentheses. (( )) represents an arithmetic environment. i=1 is the starting value, i<=101 is the comparison condition, and i++ is the increment operation after each iteration. Conditions in the for loop cannot use square brackets.

Supplementary Explanation

In arithmetic operations, variables can be used directly without the $ symbol for expansion because arithmetic expansion is a special syntax in Shell. Within an arithmetic environment, the Shell automatically recognizes and expands variables.

This behavior differs from general commands, where the $ symbol is needed to expand variables. For example, using echo $variable in the command line will print the value of the variable variable.

However, in an arithmetic environment, the Shell recognizes the variables and treats them as numerical values for computation without explicitly using the $ symbol.

Here is an example demonstrating the use of variables directly in arithmetic operations:

1
2
3
4
x=5
y=3
z=$((x + y))
echo $z # Output 8

In this example, the variables x and y are directly used in the arithmetic operation, and the Shell automatically expands them and computes the result.

It is important to note that this special variable expansion behavior applies only to arithmetic operations and does not apply to general commands or string manipulation. Therefore, in general commands, the $ symbol is still required to expand a variable to retrieve its value.

Using the above script as an example, it can also be rewritten as:

1
2
3
4
5
6
7
8
#!/bin/bash

sum=0
for ((i = 0; i < 101; i++))
do
sum=$[ sum + i ]
done
echo $sum

The output will be the same.

In arithmetic operations, $(()) and $[] are both used for arithmetic expansion in Shell. They serve similar purposes but have some syntactical differences:

  1. $(()): This is a more modern and recommended syntax for arithmetic expansion. Inside $((...)), you can perform mathematical operations, variable assignments, and logical operations. For example:

    1
    2
    3
    x=$((5 + 3))   # Variable x is assigned 8
    y=$((x * 2)) # Variable y is assigned 16
    z=$((y % 4)) # Variable z is assigned 0
  2. $[]: This is an older syntax for arithmetic expansion, which has been deprecated in newer Shell versions. It performs similarly to $(()) but does not support logical operators. For example:

    1
    2
    3
    x=$[5 + 3]   # Variable x is assigned 8
    y=$[x * 2] # Variable y is assigned 16
    z=$[y % 4] # Variable z is assigned 0

let is a built-in Shell command used to perform arithmetic operations and variable assignments. It is a more traditional method of performing calculations and differs from $((...)) and $[...] syntax. The general syntax for the let statement is:

1
let <expression>

<expression> is the arithmetic expression to be evaluated, which can include variables, mathematical operators, and constants.

Here are some examples showing how to use the let statement for arithmetic operations and variable assignments:

1
2
3
4
5
6
7
let x=5+3      # Variable x is assigned 8
let y=x*2 # Variable y is assigned 16
let z=y%4 # Variable z is assigned 0

echo $x # Output 8
echo $y # Output 16
echo $z # Output 0

The let statement can also be used to increment or decrement variables:

1
2
3
let counter=1
let counter++ # Equivalent to counter=counter+1, increments counter by 1
echo $counter # Output 2

It is important to note that variables in the let statement do not require the $ symbol for reference. You can use the variable names directly. Additionally, let does not support logical operators, and it only supports pure arithmetic operations and variable assignments.

In summary, $(()) is the modern and recommended syntax for arithmetic expansion, offering more functionality and compatibility across most Shell versions. In contrast, $[] is an outdated syntax that does not support logical operators, so it is recommended to use $(()) for arithmetic operations. If the task only involves arithmetic operations and variable assignments, you can also use the let statement, which is more intuitive for users familiar with other programming languages.

Again, using the above script as an example, the script can be written with the let statement as:

1
2
3
4
5
6
7
8
#!/bin/bash

sum=0
for ((i = 0; i < 101; i++))
do
let sum+=i
done
echo $sum

This will also produce the correct output.

Syntax 2 (foreach Enhanced For Loop)

The foreach enhanced for loop is used to iterate over a list of values. It provides a more straightforward way to iterate through elements without explicitly managing the loop index. Here’s the syntax for the foreach loop:

1
2
3
4
for variable in value1 value2 value3...
do
program
done

Example:

1
[root@lht scripts]# vim foreach.sh
1
2
3
4
5
6
7
8
#!/bin/bash

sum=0
for i in {1..100}
do
let sum+=i
done
echo $sum

When you execute the script, it will produce the sum of numbers from 1 to 100:

1
2
3
[root@lht scripts]# chmod +x foreach.sh
[root@lht scripts]# foreach.sh
5050

The foreach loop is a more readable alternative to a standard for loop when you need to iterate over a set of fixed values or a range of numbers.

Difference Between $* and $@

Both $* and $@ are used to represent all the arguments passed to a script or function, but they behave differently when they are enclosed in double quotes.

  1. Without quotes:
    When not enclosed in quotes, both $* and $@ behave similarly and represent all the positional parameters as a single string, separated by spaces:

    Example:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash

    echo "Using \$* to print all arguments:"
    for arg in $*; do
    echo $arg
    done

    echo "Using \$@ to print all arguments:"
    for arg in $@; do
    echo $arg
    done

    If you run the script with the arguments arg1 arg2 arg3:

    1
    $ ./script.sh arg1 arg2 arg3

    The output will be:

    1
    2
    3
    4
    5
    6
    7
    8
    Using $* to print all arguments:
    arg1
    arg2
    arg3
    Using $@ to print all arguments:
    arg1
    arg2
    arg3
  2. With quotes:
    The difference becomes apparent when you enclose $* and $@ in double quotes.

    • "$*" treats all the parameters as a single string and outputs them as a single word, separated by spaces.
    • "$@" treats each parameter as a separate quoted string.

    Example:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash

    echo "Using \"\$*\" to print all arguments:"
    for arg in "$*"; do
    echo $arg
    done

    echo "Using \"\$@\" to print all arguments:"
    for arg in "$@"; do
    echo $arg
    done

    If you run the script with the arguments arg1 arg2 arg3, the output will be:

    1
    2
    3
    4
    5
    6
    Using "$*" to print all arguments:
    arg1 arg2 arg3
    Using "$@" to print all arguments:
    arg1
    arg2
    arg3
    • "$*" outputs all the arguments as a single string: arg1 arg2 arg3.
    • "$@" outputs each argument separately: arg1, arg2, arg3.

While Loop

The while loop is used for repeated execution of a program as long as a certain condition is true. The syntax is as follows:

1
2
3
4
while [ condition ]
do
program
done

Example:

1
2
3
4
5
6
7
8
#!/bin/bash

counter=1
while [ $counter -le 5 ]
do
echo "Loop count: $counter"
counter=$((counter + 1))
done

In this example, the loop will continue executing as long as the counter is less than or equal to 5. Each iteration prints the current count and increments the counter.

The output will be:

1
2
3
4
5
Loop count: 1
Loop count: 2
Loop count: 3
Loop count: 4
Loop count: 5

The loop will stop when the condition [ $counter -le 5 ] is no longer true. The operator -le stands for “less than or equal” and is used to compare the value of counter to 5.

  • Title: Shell Scripts 1
  • Author: LHT
  • Created at : 2023-01-15 01:10:00
  • Link: https://blog.327774.xyz/2023/01/14/linux/shell/Shell Scripts 1/
  • License: This work is licensed under CC BY-NC-SA 4.0.