Skip to content

第六章 Shell脚本 v1.0 ​

第一部分 Shell脚本简介 ​

一、Shell脚本基本结构 ​

bash
#!/bin/bash    # 脚本第一行,指定解释器
# ...          # 脚本主体

Shell脚本主体中一般包括几个部分,简单示例如下:

bash
# 这是一行注释
bash
name="Mike"
bash
echo "Hello, $name!"
bash
if [ $a -eq $b ]; then
    echo "a等于b"
fi
bash
function hello() {
    echo "Hello, $name!"
}

二、注释与文档 ​

注释和文档是编写Shell脚本的重要部分,有助于提高代码的可读性和可维护性。

bash
# 这是一行注释
bash
:<<'COMMENT'
这是一个多行注释
COMMENT

第二部分 基本语法 ​

一、变量与常量 ​

变量是存储数据的容器,常量在Shell中没有严格的定义,通常通过变量实现

bash
name="Mike"
bash
echo "Hello, $name"  # 适用于简单的变量引用,且后面没有其他字符
echo "Bye, ${name}"  # 更安全的写法,适用于任何情况尤其是变量跟有其他字符时

二、数据类型 ​

Shell脚本中的数据类型较为简单,主要包括字符串、整数与数组三种。

bash
# 默认数据类型
str="string"
bash
num=10
bash
arr=("apple" "banana" "cherry")

三、字符串操作 ​

Shell支持多种字符串操作,常用的如下

bash
str1="Hello"
str2="World"
result="$str1 $str2"  # Hello World
bash
str="Hello"
len=${#str}  # 5
bash
str="Hello, World!"
substr=${str:7:5}  # World
bash
str="Hello, World!"
new_str=${str//o/O}  # HellO, WOrld!

四、数字运算 ​

Shell脚本支持基本的数字运算,但需要使用 expr 或 $((...))

bash
a=5
b=3
sum=$((a + b))       # 8  '+':加法
diff=$((a - b))      # 2  '-':减法
product=$((a * b))   # 15 '*':乘法
quotient=$((a / b))  # 1  '/':除法
remainder=$((a % b)) # 2  '%':取余

五、数组 ​

Shell脚本支持数组,用于存储多个值

bash
arr=("apple" "banana" "cherry")
bash
echo ${arr[0]}  # apple
bash
len=${#arr[@]}  # 3
bash
for item in "${arr[@]}"; do
    echo $item
done

第三部分 输入输出 ​

一、标准输入输出 ​

Shell脚本通过标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)进行交互。

bash
read name  # 从键盘读取输入
bash
echo "Hello, $name!"  # 输出到终端
bash
echo "错误信息" >&2  # 输出到标准错误

二、文件读写操作 ​

bash
# 全部读取
cat file.txt
# 逐行读取
while IFS= read -r line; do
    echo "$line"
done < file.txt
bash
echo "Hello, World!" > file.txt    # 覆盖写入
echo "Append content" >> file.txt  # 追加写入

三、管道与重定向 ​

管道和重定向用于控制数据流

1.管道 ​

将一个命令的输出作为另一个命令的输入

bash
echo "Hello, World!" | tr '[:lower:]' '[:upper:]'  # HELLO, WORLD!

2.重定向 ​

bash
echo "Hello" > file.txt  # 覆盖写入
echo "World" >> file.txt  # 追加写入
bash
while IFS= read -r line; do
    echo $line
done < file.txt
bash
command 2> error.log  # 将错误信息写入error.log
bash
command &> output.log  # 将标准输出和错误输出合并到output.log

第四部分 控制结构 ​

一、条件表达式 ​

在Shell脚本中,条件表达式用于在 if、while、until 等控制结构中进行条件判断

1.一般比较 ​

bash
"$a" -eq "$b"  # 判断a等于b
"$a" -ne "$b"  # 判断a不等于b
"$a" -lt "$b"  # 判断a小于b
"$a" -le "$b"  # 判断a小于或等于b
"$a" -gt "$b"  # 判断a大于b
"$a" -ge "$b"  # 判断a大于或等于b
bash
"$a" = "$b"   # 判断字符串相等
"$a" != "$b"  # 判断字符串不相等
-z "$a"       # 判断字符串长度为零
-n "$a"       # 字符串长度非零

2.文件条件 ​

bash
-e "path/to/file" # 指定文件存在
-f "path/to/file" # 指定文件存在且为普通文件
-d "path/to/file" # 指定文件存在且为目录
-r "path/to/file" # 指定文件存在且可读
-w "path/to/file" # 指定文件存在且可写
-x "path/to/file" # 指定文件存在且可执行
-s "path/to/file" # 指定文件存在且大小大于零

3.高级判断 ​

[[ ]] 是 Bash 的扩展条件测试命令,支持更多的特性,如模式匹配和正则表达式。

bash
# 字符串比较
# 判断字符串a小于b
[[ "$a" < "$b" ]]  
# 判断字符串a大于b
[[ "$a" > "$b" ]]  

# 字符串匹配 (注意匹配时模式不要加引号,否则会当做普通字符处理)
# 简单匹配,支持 *(匹配任意长度任意字符)、?(匹配单个任意字符)、[...](匹配括号中的任意一个字符) 等通配符
[[ "$a" == $exp ]]
# 正则表达式匹配,支持完整正则表达式匹配
[[ "$a" =~ $exp ]]

二、条件结构 ​

1.if语句 ​

bash
if [ 条件表达式1 ]; then
  # 当条件1为真时执行的命令
elif [ 条件表达式2 ]; then
  # 当条件2为真时执行的命令
else
  # 当所有条件都为假时执行的命令
fi
bash
number=0
if [ $number -eq 0 ];then  
  echo "number = 0"        # 输出
else 
  echo "number != 0"       # 未输出
fi

2.case语句 ​

bash
case $variable in
  pattern1)
    # 当变量匹配 pattern1 时执行的命令
    ;;
  pattern2)
    # 当变量匹配 pattern2 时执行的命令
    ;;
  *)
    # 当变量不匹配任何模式时执行的命令
    ;;
esac
bash
read -p "Enter a number: " number
case $number in
  1)
    echo "You entered one."
    ;;
  2)
    echo "You entered two."
    ;;
  3)
    echo "You entered three."
    ;;
  *)
    echo "You entered a number other than 1, 2, or 3."
    ;;
esac

三、循环结构 ​

1.for循环 ​

bash
for var in list; do  # list中元素以空格分隔
  # 对于列表中的每个元素执行的命令
done
bash
list="1 2 3 4 5"

for i in $list; do
  echo "Number: $i"
done
# 依次输出: Number: 1 ~ Number: 5

for i in "$list"; do
  echo "Number: $i"
done
# 输出: Number: 1 2 3 4 5

2.while循环 ​

先判断条件表达式的值,当条件为真时执行循环体,然后再次判断条件

bash
while [ condition ]; do
  # 当条件为真时执行的命令
done
bash
count=1
while [ $count -le 5 ]; do
  echo "Count: $count"
  count=$((count + 1))
done

3.until循环 ​

先执行循环体,然后判断条件表达式的值,当条件为假时继续执行循环体,直到条件为真时结束循环

bash
until [ condition ]; do
  # 先执行一次循环体,当条件为假时继续执行
done
bash
count=1
until [ $count -gt 5 ]; do
  echo "Count: $count"
  count=$((count + 1))
done

第五部分 函数 ​

一、函数定义 ​

bash
function function_name {
    # 函数体
    command1
    command2
    ...
}
bash
function_name() {
    # 函数体
    command1
    command2
    ...
}

二、函数调用 ​

定义好函数后,可以在脚本的其他地方直接通过函数名调用

bash
function_name [参数]

三、函数参数 ​

函数可以接受参数,这些参数在函数内部通过位置变量 $1, $2, ..., $n 来访问,其中 $1 表示第一个参数,$2 表示第二个参数,依此类推。在Shell脚本中,特殊变量 $0 表示脚本本身的名称,而不是函数名。

bash
# 带参数的函数示例
hello() {
    echo "Hello, $1! Welcome to $2."
}

# 调用函数并传递参数
hello "Alice" "Shell Programming"

四、函数返回值 ​

Shell中返回值一般指函数的退出状态码,用于表示函数执行的成功(0)或失败(非0)。

默认情况下,函数的退出状态码是最后一条命令的退出状态码。可以使用 return 语句显式设置退出状态码。

bash
function check_number() {
    if [ $1 -lt 0 ]; then
        echo "Error: Number is negative"
        return 1  # 函数返回状态码1
    fi
    echo "Number is positive"
    return 0  # 函数返回状态码0
}
bash
check_number -5
echo $?          # 输出:1
check_number 5
echo $?          # 输出:0

另一种概念的返回值是输出返回值,函数可以通过echo返回值,调用时可以捕获这些输出。

bash
calculate_sum() {
    local sum=$(( $1 + $2 ))
    echo $sum
}

# 捕获函数的输出
result=$(calculate_sum 5 3)
echo "The sum is: $result"  # 输出:The sum is: 8

五、变量作用域 ​

Shell中,变量的作用域有全局和局部之分

  • 全局变量:在函数外部定义的变量或在函数内部未使用 local 关键字声明的变量是全局变量,可以在函数内外访问
  • 局部变量:在函数内部使用 local 关键字声明的变量是局部变量,仅在函数内部可见
bash
global_var=10

my_function() {
    local local_var=20  # 局部变量
    echo "Inside function: global_var=$global_var, local_var=$local_var"
    # Inside function: global_var=10, local_var=20
}

my_function
echo "Outside function: global_var=$global_var, local_var=$local_var"
# Outside function: global_var=10, local_var=

第六部分 文本处理 ​

一、grep ​

用于在文本中搜索匹配特定模式的行,并将匹配的行打印出来

bash
grep [选项] '搜索词' 文件
# 常用选项
# -v:反转匹配,显示不符合模式的行
# -E:使用扩展正则表达式
# -i:忽略大小写
# -w:全词匹配
# -n:显示匹配行的行号
# -r:递归搜索目录中的文件
bash
grep "a" example.txt                 # 搜索 example.txt 文件中含有a的行
grep -i "a" example.txt              # 搜索 example.txt 文件中包含 "a" 且忽略大小写的行
grep -v "a" example.txt              # 搜索 example.txt 文件中不包含 "a" 的行
grep -r 'hello' .                    # 递归搜索当前目录下所有文件中包含 "hello" 的行
grep -E 'apple|banana' example.txt   # 使用扩展正则表达式搜索包含 "apple" 或 "banana" 的行

二、sed ​

流编辑器,能够在不修改原始文件的情况下,对文本进行快速高效的编辑操作

bash
sed [选项] '命令' 文件
# 常用选项
# -n:默认情况下,sed会输出处理后的所有行,-n选项只输出经过sed命令处理的行
# -e:允许在同一命令行中执行多个sed命令
# -i:直接修改文件内容(修改前建议备份文件)
# -f:从文件中读取sed命令
bash
sed 's/apple/orange/g' example.txt              # 将example.txt文件中每一行的apple替换为orange
sed -n '2p' example.txt                         # 只打印example.txt文件的第2行
sed '1,3d' example.txt                          # 删除example.txt文件中第1到第3行
sed 's/^/#/g' example.txt                       # 在example.txt文件每一行开头添加#
sed -i's/old/new/g' example.txt                 # 直接修改example.txt文件,将old替换为new

三、awk ​

文本处理工具,能够逐行处理文本,通过指定的模式匹配和操作对数据进行提取、转换和输出

bash
awk [选项] '模式 {操作}' 文件
# 常用选项
# -F:指定输入字段分隔符
# -v:定义变量
# 常用模式
# BEGIN:在处理文件内容之前执行一次操作
# END:在处理完文件所有内容之后执行一次操作
# 常用操作
# $1、$2...:表示第1个、第2个...字段
# NF:表示当前行的字段数量
# NR:表示当前处理的行号
bash
awk -F: '{print $1}' /etc/passwd                     # 以:为分隔符,打印/etc/passwd文件中的第1个字段(用户名)
awk '{print $NF}' example.txt                        # 打印example.txt文件中每一行的最后一个字段
awk 'NR==10' example.txt                             # 打印example.txt文件的第10行
awk -v sum=0 '{sum+=$1} END {print sum}' data.txt    # 计算data.txt文件中第一列数字的总和
awk 'BEGIN {FS=","; OFS=":"} {print $1,$2}' data.csv # 以逗号为输入分隔符,冒号为输出分隔符,打印data.csv文件的前两列