# Shell脚本编写

参照《Linux命令行大全》一书,学习如何编写Shell脚本。

# 第一个脚本-HelloWorld

[root@VM-8-5-centos shells]# ls
hello_world.sh
[root@VM-8-5-centos shells]# cat hello_world.sh
#!/bin/bash

# This is the first shell

echo 'Hello World!'
[root@VM-8-5-centos shells]# sh hello_world.sh
Hello World!

# 在脚本中使用变量及常量

[root@VM-8-5-centos shells]# > sys_info_page.sh
[root@VM-8-5-centos shells]# vim sys_info_page.sh
[root@VM-8-5-centos shells]# cat sys_info_page.sh
#!/bin/sh

#在shell中添加变量及使用环境变量

TITLE="System Information Report For $HOSTNAME"

echo "<html>
    <head>
        <title>$TITLE</title>
    </head>
    <body>
        <h1>$TITLE</h1>
    </body>
</html>"
[root@VM-8-5-centos shells]# sh sys_info_page.sh
<html>
    <head>
        <title>System Information Report For VM-8-5-centos</title>
    </head>
    <body>
        <h1>System Information Report For VM-8-5-centos</h1>
    </body>
</html>

# 在脚本中使用Shell函数

[root@VM-8-5-centos shells]# cat sys_info_page.sh
#!/bin/sh

#在shell中添加变量及使用环境变量
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"

report_uptime(){
        # _EOF_块中间一定不能出现空格
        cat <<-_EOF_
        <h2>System Uptime</h2>
        <pre>$(uptime)</pre>
        _EOF_
    return
}

report_disk_space(){
        cat <<-_EOF_
        <h2>Disk Space Utilization</h2>
        <pre>$(df -h)</pre>
        _EOF_
    return
}

report_home_space(){
        cat <<-_EOF_
        <h2>Home Space Utilization</h2>
        <pre>$(du -sh /home/*)</pre>
        _EOF_
    return
}
echo "<html>
    <head>
        <title>$TITLE</title>
    </head>
    <body>
        <h1>$TITLE</h1>
        <p>$TIMESTAMP</p>
        $(report_uptime)
        $(report_disk_space)
        $(report_home_space)
    </body>
</html>"
[root@VM-8-5-centos shells]# sh sys_info_page.sh
<html>
    <head>
        <title>System Information Report For VM-8-5-centos</title>
    </head>
    <body>
        <h1>System Information Report For VM-8-5-centos</h1>
        <p>Generated 01/15/2024 04:05:47 PM CST, by root</p>
        <h2>System Uptime</h2>
<pre> 16:05:47 up 23 days, 17:59,  2 users,  load average: 0.00, 0.01, 0.05</pre>
        <h2>Disk Space Utilization</h2>
<pre>Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        908M     0  908M   0% /dev
tmpfs           919M   24K  919M   1% /dev/shm
tmpfs           919M  664K  919M   1% /run
tmpfs           919M     0  919M   0% /sys/fs/cgroup
/dev/vda1        50G  4.9G   43G  11% /
tmpfs           184M     0  184M   0% /run/user/0
overlay          50G  4.9G   43G  11% /var/lib/docker/overlay2/1d84ba15ebf6364747fbcfd77cde318d291d35056382d522237b39b7405ad914/merged</pre>
        <h2>Home Space Utilization</h2>
<pre>40K        /home/lighthouse</pre>
    </body>
</html>

# 在脚本中使用流控制-if判断

if判断的语法是一个if关键词后配置一个test表达式形式,如if [-b file] 判断指定文件是否是一个块文件。如if [[ expression ]]。如(())表达式用于执行算数真值测试。

  • 文件表达式
表达式 说明
file1 -ef file2 file1和file2拥有相同的信息节点编号(这两个文件通过硬链接指向同一个文件)。
file1 -nt file2 file1比file2新。
file1 -ot file2 file1比file2旧。
-b file file存在并且是一个块设备文件。
-c file file存在且是一个字符设备文件。
-d file file存在且是一个目录
-e file file存在。
-f file file存在且是一个普通文件
-g file file存在且设置了组id。
-G file file存在且设置了有效组id。
-L file file存在且是一个符号链接。
-O file file存在且属于有效用户id。
-p file file存在且是一个命名管道。
-r file file存在且可读。
-s file file存在并且其长度大于0。
-S file file存在且是一个网络套接字(Socket)。
-w file file存在并且可写。
-x file file存在并且可执行。
  • 字符串表达式
表达式 说明
string string不为空。
-n string string的长度大于0。
-z string string的长度等于0。
string1==string2 string1和string2相等。
string1!=string2 string1和string2不相等。
string1>string2 在排序时,string1在string2之后。
  • 整数表达式
表达式 说明
int1 -eq int2 int1和int2相等。
int1 -ne int2 int1和int2不相等。
int1 -le int2 int1小于等于int2。
int1 -lt int2 int1小于int2。
int1 -ge int2 int1大于等于int2。
int1 -gt int2 int1大于int2。
[root@VM-8-5-centos shells]# cat testif.sh
#!/bin/bash

x=5
# 需要注意,[后面和]前面必须要有空格
if [ $x -eq 5 ];then
        echo "x equals 5"
else
        echo "x not equals 5"
fi
[root@VM-8-5-centos shells]# sh testif.sh
x equals 5
[root@VM-8-5-centos shells]# cat test_file.sh
#!/bin/bash

FILE=~/.bashrc
# if 和 [ 之间也要加空格,不然会报错
if [ -e "$FILE" ];then
        if [ -f "$FILE" ]; then
                echo "$FILE is a regular file."
        fi

        if [  -d "$FILE" ]; then
                echo "$FILE is a directory."
        fi

        if [ -r "$FILE" ]; then
                echo "$FILE is readable."
        fi

        if [ -w "$FILE" ]; then
                echo "$FILE is writable."
        fi
else
        echo "$FILE does not exist"
        exit 1
fi
# 退出
exit
[root@VM-8-5-centos shells]# sh test_file.sh
/root/.bashrc is a regular file.
/root/.bashrc is readable.
/root/.bashrc is writable.
[root@VM-8-5-centos shells]# cat test_string.sh
#!/bin/bash

ANSWER=maybe

if [ -z "$ANSWER" ]; then
        echo "There is no answer." >&2
        exit 1
fi

if [ "$ANSWER" = "yes" ]; then
        echo "The answer is yes."
elif [ "$ANSWER" = "no" ]; then
        echo "The answer is no."
elif [ "$ANSWER" = "maybe" ]; then
        echo "The answer is maybe."
else
        echo "The answer is unknown."
fi
[root@VM-8-5-centos shells]# sh test_string.sh
The answer is maybe.
[root@VM-8-5-centos shells]# cat test_int.sh
#!/bin/bash

INT=5

if [ -z "$INT" ]; then
        echo "INT is empty." >&2
        exit 1
fi

if [ "$INT" -eq 0 ]; then
        echo "INT is zero."
else
        if [ "$INT" -lt 0 ]; then
                echo "INT is negative."
        else
                echo "INT is positive."
        fi

        if [ $((INT % 2)) -eq 0 ]; then
                echo "INT is even."
        else
                echo "INT is odd."
        fi
fi
[root@VM-8-5-centos shells]# sh test_int.sh
INT is positive.
INT is odd.
[root@VM-8-5-centos shells]# cat test_int2.sh
#!/bin/bash

INT=5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then

        if [ "$INT" -eq 0 ]; then
                echo "INT is zero."
        else
                if [ "$INT" -lt 0 ]; then
                        echo "INT is negative."
                else
                        echo "INT is positive."
                fi

                if [ $((INT % 2)) -eq 0 ]; then
                        echo "INT is even."
                else
                        echo "INT is odd."
                fi
        fi

else
        echo "INT is not an integer." >&2
        exit 1
fi
[root@VM-8-5-centos shells]# sh test_int2.sh
INT is positive.
INT is odd.
[root@VM-8-5-centos shells]# cat test_int3.sh
#!/bin/bash

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then

        if ((INT == 0)); then
                echo "INT is zero."
        else
                if (( INT < 0)); then
                        echo "INT is negative."
                else
                        echo "INT is positive."
                fi

                if (( ((INT % 2)) == 0)); then
                        echo "INT is even."
                else
                        echo "INT is odd."
                fi
        fi

else
        echo "INT is not an integer." >&2
        exit 1
fi
[root@VM-8-5-centos shells]# sh test_int3.sh
INT is negative.
INT is odd.

# 在脚本中读取键盘输入

使用read指令,可以从标准输入中读取输入值。

  • read指令相关选项
选项 说明
-a array 将输入值从索引为0的位置开始赋值给array数组。
-d delimiter 用字符串delimiter的第一个字符标志输入的结束,而不是新的一行的开始。
-n num 从输入中读取num个字符,而不是一整行。
-p prompt 使用prompt字符串提示用户进行输入。
-s 保密模式,不在屏幕显示输入的字符。
-t seconds 超时。在seconds秒后结束输入。
[root@VM-8-5-centos shells]# cat read_int.sh
#!/bin/bash

echo -n "Please enter an int. -> "
read INT

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then

        if [ "$INT" -eq 0 ]; then
                echo "INT is zero."
        else
                if [ "$INT" -lt 0 ]; then
                        echo "INT is negative."
                else
                        echo "INT is positive."
                fi

                if [ $((INT % 2)) -eq 0 ]; then
                        echo "INT is even."
                else
                        echo "INT is odd."
                fi
        fi

else
        echo "INT is not an integer." >&2
        exit 1
fi
[root@VM-8-5-centos shells]# sh read_int.sh
Please enter an int. -> 3
INT is positive.
INT is odd.

# 在脚本中使用流控制-while循环

[root@VM-8-5-centos shells]# cat while_count.sh
#!/bin/bash -x

count=1

while [ $count -le 5 ]; do
        echo $count
        count=$((count + 1))
done

echo "Finished."
[root@VM-8-5-centos shells]# sh while_count.sh
1
2
3
4
5
Finished.
[root@VM-8-5-centos shells]# cat while-menu2.sh
#!/bin/bash

DELAY=3

while true;do
        clear
        cat <<- _EOF_
                Please Select:

                1.Display System Information
                2.Display Disk Space
                3.Display Home Space Utilization
                Q.Quit
        _EOF_
        read -p "Enter Selection [0-3] >"

        if [[ $REPLY =~ ^[0-3]$ ]];then
                if [[ $RELAY == 1 ]];then
                        echo "Hostname: $HOSTNAME"
                        uptime
                        sleep $DELAY
                        continue;
                fi

                if [[ $RELAY == 2 ]];then
                        df -h
                        sleep $DELAY
                        continue;
                fi

                if [[ $REPLY == 3 ]];then
                        echo "hello"
                        sleep $DELAY
                        continue;
                fi

                if [[ $REPLY == 0 ]];then
                        break;
                fi
        else
                echo "Invalid entry."
                sleep $DELAY
        fi
done
echo "Program terminated."

# 在脚本中使用流控制-case分支

[root@VM-8-5-centos shells]# cat case-menu.sh
#!/bin/bash

clear
echo "
Please Select:
1.Display System Information
2.Display Disk Space
3.Display Home Space Utilization
Q.Quit
"
read -p "Enter Selection [0-3] >"

case $REPLY in
        0)      echo "Program terminated."
                exit
                ;;
        1)      echo "Hostname: $HOSTNAME"
                uptime
                ;;
        2)      df -h
                ;;
        3)      echo "hello"
                exit 1
                ;;
esac
[root@VM-8-5-centos shells]# sh case-menu.sh

Please Select:
1.Display System Information
2.Display Disk Space
3.Display Home Space Utilization
Q.Quit

Enter Selection [0-3] >2
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        908M     0  908M   0% /dev
tmpfs           919M   24K  919M   1% /dev/shm
tmpfs           919M  640K  919M   1% /run
tmpfs           919M     0  919M   0% /sys/fs/cgroup
/dev/vda1        50G  4.9G   43G  11% /
tmpfs           184M     0  184M   0% /run/user/0
overlay          50G  4.9G   43G  11% /var/lib/docker/overlay2/1d84ba15ebf6364747fbcfd77cde318d291d35056382d522237b39b7405ad914/merged

# 在脚本中使用位置参数

[root@VM-8-5-centos shells]# cat posit-param.sh
#!/bin/bash

echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"
[root@VM-8-5-centos shells]# sh posit-param.sh

Number of arguments: 0
$0 = posit-param.sh
$1 =
$2 =
$3 =
$4 =
$5 =
$6 =
$7 =
$8 =
$9 =

[root@VM-8-5-centos shells]# sh posit-param.sh 1 2 3

Number of arguments: 3
$0 = posit-param.sh
$1 = 1
$2 = 2
$3 = 3
$4 =
$5 =
$6 =
$7 =
$8 =
$9 =

# 在脚本中使用流控制-for循环

[root@VM-8-5-centos shells]# cat simple_count.sh
#!/bin/bash

for (( i=0; i<5; i=i+1 )); do
        echo $i
done
[root@VM-8-5-centos shells]# sh simple_count.sh
0
1
2
3
4