Bash 脚本有两个部分:bash和script。Bash 是一个 Unix shell,由 GNU 项目开发。脚本只是一组在文件中编写并使用 bash 执行的命令。Bash 不是市场上唯一的 shell,但它是最受欢迎的 shell。
一、引言
Bash脚本是由命令行指令和脚本语言语法构成的一种强大工具,用于自动化执行各种任务。作为Unix和Linux系统的默认命令行解释器,Bash(Bourne Again SHell)在系统管理、批处理操作以及应用程序开发中扮演着至关重要的角色。通过编写Bash脚本,用户可以简化日常重复性操作,提升工作效率,并减少人为错误的可能性。
Bash脚本不仅适用于系统管理员和开发人员,任何希望在Linux或MacOS环境中实现任务自动化的用户都可以从中受益。无论是定时备份重要数据、批量处理文件、监控系统性能,还是部署应用程序,Bash脚本都提供了一种简洁而高效的解决方案。
Bash是Bourne Again SHell的缩写,是一种UNIX shell和命令语言,作为GNU操作系统的一部分被广泛使用。它是由Brian Fox为GNU项目编写的自由软件,是Bourne Shell(sh)的替代品,并兼容sh的语法,同时增加了许多增强功能。Bash不仅可以作为命令行界面使用,还可以用于编写脚本,自动化处理一系列任务。
二、基础语法
1. 基本构成
- 第一行必须是
#!/bin/bash
- 注释使用
#
开头 - 执行脚本使用
./脚本名
或者bash 脚本名
#!/bin/bash
# 这是注释
# 声明变量并打印
my_var="Hello, World!"
echo $my_var
# 获取输入并打印
read -p "Enter your first name: " firstname
read -p "Enter your last name: " lastname
echo "Hello, $firstname $lastname!"
2. 变量类型
- 自定义变量:
- 字符变量
- 数组变量
- 关联数组
- 系统变量:
- 内建变量:
$IFS
: 内部字段分隔符,用于拆分字符串$LINENO
: 当前脚本行号$OLDPWD
: 前一个工作目录$PWD
: 当前工作目录$RANDOM
: 生成一个随机数$SECONDS
: 脚本运行的秒数$UID
: 当前用户的用户ID
- 特殊变量:
$0
: 脚本的名称$1, $2
, …: 传递给脚本的参数$@
: 所有参数的列表$#
: 参数的个数$?
: 上一个命令的退出状态$$
: 当前脚本的进程ID$!
: 最后一个后台进程的进程ID$_
: 最后一个命令的最后一个参数或最后一个命令的输出
3. 常见命令
-
echo
: 输出字符串到终端。echo "Hello, World!"
-
ls
: 列出目录内容。ls
-
cd
: 改变当前目录。cd /path/to/directory
-
cp
: 复制文件或目录。cp source_file destination
-
mv
: 移动或重命名文件或目录。mv old_name new_name
-
rm
: 删除文件或目录。rm filename
-
touch
: 创建一个空文件或更新文件的修改时间。touch newfile.txt
-
mkdir
: 创建新目录。mkdir new_directory
-
输入重定向: 将文件内容作为命令的输入。
command < inputfile
-
输出重定向: 将命令的输出写入文件。
command > outputfile
-
追加输出: 将命令的输出追加到文件末尾。
command >> outputfile
-
错误输出重定向: 将错误信息写入文件。
command 2> errorfile
-
管道: 将一个命令的输出作为另一个命令的输入。
command1 | command2
例如,将
ls
命令的输出通过管道传递给grep
命令进行过滤:ls | grep "pattern"
三、控制结构
1. 条件判断
Bash脚本中常用的条件判断包括if
语句、case
语句等。
-
if语句: 用于条件判断。
if [ condition ]; then # 当条件为真时执行的代码 elif [ another_condition ]; then # 当另一个条件为真时执行的代码 else # 当条件为假时执行的代码 fi
例如,判断一个文件是否存在:
if [ -f "file.txt" ]; then echo "file.txt exists." else echo "file.txt does not exist." fi
-
case语句: 用于多分支条件判断。
case $variable in pattern1) # 当变量匹配pattern1时执行的代码 ;; pattern2) # 当变量匹配pattern2时执行的代码 ;; *) # 当变量不匹配上述任意模式时执行的代码 ;; esac
例如,根据输入的值执行不同的操作:
read -p "Enter a number: " number case $number in 1) echo "You entered one." ;; 2) echo "You entered two." ;; *) echo "You entered something else." ;; esac
2. 循环结构
Bash脚本中常用的循环结构包括for
循环、while
循环等。
-
for循环: 用于遍历列表中的每一个元素。
for variable in list; do # 对于列表中的每一个元素执行的代码 done
例如,遍历数字1到5:
for i in {1..5}; do echo "Number: $i" done
-
while循环: 用于在条件为真时反复执行代码。
while [ condition ]; do # 当条件为真时执行的代码 done
例如,循环输出直到变量等于5:
count=1 while [ $count -le 5 ]; do echo "Count: $count" ((count++)) done
-
until循环: 与
while
循环相反,用于在条件为假时反复执行代码。until [ condition ]; do # 当条件为假时执行的代码 done
例如,循环输出直到变量等于5:
count=1 until [ $count -gt 5 ]; do echo "Count: $count" ((count++)) done
四、函数
Bash脚本中的函数是一组可以重复使用的命令和语句块,可以接受参数并返回值。使用函数可以提高脚本的可读性和可维护性,减少代码重复。
#!/bin/bash
# 定义函数
greet() {
local name=$1
echo "Hello, $name!"
}
add() {
local num1=$1
local num2=$2
local sum=$(( num1 + num2 ))
echo $sum
}
# 调用函数
greet "Alice"
result=$(add 10 20)
echo "Sum: $result"
# 使用带参数的函数
calculate_area() {
local length=$1
local width=$2
local area=$(( length * width ))
echo $area
}
area=$(calculate_area 5 10)
echo "Area: $area"
1. 定义函数
函数的定义非常简单,通常使用以下两种方式之一:
function_name() {
# 函数体
}
# 或者
function function_name {
# 函数体
}
2. 调用函数
定义函数后,可以通过函数名来调用它:
function_name
3. 函数参数
函数可以接受参数,这些参数在函数体内通过位置参数变量 $1
, $2
, … 来访问。
greet() {
echo "Hello, $1!"
}
greet "Alice"
在这个例子中,函数 greet
接受一个参数并输出问候语。
4. 返回值
函数可以通过 return
语句返回一个整数值,表示函数的退出状态。也可以通过修改全局变量或使用 echo
输出值来返回字符串或其他类型的数据。
add() {
result=$(( $1 + $2 ))
return $result
}
add 3 5
sum=$?
echo "Sum: $sum"
在这个例子中,函数 add
返回两个参数的和,并使用 $?
获取返回值。
concat() {
echo "$1 $2"
}
result=$(concat "Hello" "World")
echo $result
在这个例子中,函数 concat
返回两个参数的拼接结果,并使用命令替换 $(...)
获取返回值。
5. 局部变量
在函数内部定义局部变量时,可以使用 local
关键字,避免变量污染全局命名空间。
calculate_square() {
local num=$1
local square=$(( num * num ))
echo $square
}
result=$(calculate_square 4)
echo "Square: $result"
在这个例子中,变量 num
和 square
是局部变量,仅在函数 calculate_square
内部可见。
五、数组
1. 普通数组
# 定义及使用
my_array=(apple banana cherry)
echo ${my_array[0]} # 输出apple
echo ${my_array[1]} # 输出banana
echo ${my_array[@]} # 输出所有元素
# 修改数组指定值
my_array[3]="date"
echo ${my_array[3]} # 输出date
2. 关联数组
# 创建关联数组
declare -A my_assoc_array
my_assoc_array=([name]="Alice" [age]=30)
echo ${my_assoc_array[name]} # 输出Alice
echo ${my_assoc_array[age]} # 输出30
六、字符串
1. 定义和使用
字符串可以直接赋值给变量,用双引号或单引号包围。
str1="Hello, World!"
str2='Hello, Bash!'
echo $str1
echo $str2
2. 字符串连接
通过简单的拼接或使用+=
操作符连接字符串。
str1="Hello,"
str2=" World!"
str3="$str1$str2"
echo $str3
str1+=" Bash!"
echo $str1
3. 获取字符串长度
使用${#variable}
语法获取字符串长度。
str="Hello, World!"
length=${#str}
echo "Length: $length"
4. 字符串截取
使用${variable:offset:length}
语法截取字符串的子字符串。
str="Hello, World!"
substring=${str:7:5} # 从索引7开始,截取5个字符
echo $substring # 输出 "World"
5. 字符串查找和替换
使用${variable/pattern/replacement}
语法查找和替换字符串中的内容。
str="Hello, World!"
new_str=${str/World/Bash}
echo $new_str # 输出 "Hello, Bash!"
要替换所有匹配的字符串,可以使用${variable//pattern/replacement}
。
str="Hello, World! Welcome to the World!"
new_str=${str//World/Bash}
echo $new_str # 输出 "Hello, Bash! Welcome to the Bash!"
6. 字符串比较
使用==
或!=
进行字符串比较。
str1="Hello"
str2="World"
if [ "$str1" == "$str2" ]; then
echo "Strings are equal"
else
echo "Strings are not equal"
fi
7. 字符串模式匹配
使用通配符进行模式匹配。
str="Hello, World!"
if [[ $str == H* ]]; then
echo "String starts with 'H'"
fi
8. 字符串分割
使用IFS
(内部字段分隔符)将字符串分割成数组。
str="one,two,three"
IFS=',' read -r -a array <<< "$str"
echo "First element: ${array[0]}"
echo "Second element: ${array[1]}"
echo "Third element: ${array[2]}"
9. 字符串大小写转换
Bash不直接支持大小写转换,但可以通过调用其他工具(如tr
)实现。
str="Hello, World!"
lowercase=$(echo $str | tr 'A-Z' 'a-z')
uppercase=$(echo $str | tr 'a-z' 'A-Z')
echo "Lowercase: $lowercase"
echo "Uppercase: $uppercase"
10. 示例综合
以下是一个综合示例,展示了上述各种字符串操作的用法:
#!/bin/bash
# 定义字符串
str1="Hello,"
str2=" World!"
str3="$str1$str2"
echo $str3 # 输出 "Hello, World!"
# 获取字符串长度
length=${#str3}
echo "Length: $length" # 输出字符串长度
# 字符串截取
substring=${str3:7:5}
echo "Substring: $substring" # 输出 "World"
# 字符串查找和替换
new_str=${str3/World/Bash}
echo "Replaced: $new_str" # 输出 "Hello, Bash!"
# 字符串比较
if [ "$str1" == "$str2" ]; then
echo "Strings are equal"
else
echo "Strings are not equal"
fi
# 字符串模式匹配
if [[ $str3 == H* ]]; then
echo "String starts with 'H'"
fi
# 字符串分割
str="one,two,three"
IFS=',' read -r -a array <<< "$str"
echo "First element: ${array[0]}" # 输出 "one"
echo "Second element: ${array[1]}" # 输出 "two"
echo "Third element: ${array[2]}" # 输出 "three"
# 字符串大小写转换
lowercase=$(echo $str3 | tr 'A-Z' 'a-z')
uppercase=$(echo $str3 | tr 'a-z' 'A-Z')
echo "Lowercase: $lowercase" # 输出 "hello, world!"
echo "Uppercase: $uppercase" # 输出 "HELLO, WORLD!"
七、文件操作
以下是一个综合示例,展示了如何进行文件的创建、读取、写入、删除等操作。
#!/bin/bash
# 创建文件
touch myfile.txt
echo "File created: myfile.txt"
# 写入文件
echo "Hello, World!" > myfile.txt
echo "Written to file: myfile.txt"
# 追加写入文件
echo "Hello, again!" >> myfile.txt
echo "Appended to file: myfile.txt"
# 读取文件
echo "Contents of myfile.txt:"
cat myfile.txt
# 逐行读取文件
echo "Reading file line by line:"
while IFS= read -r line; do
echo "$line"
done < "myfile.txt"
# 检查文件是否存在
if [ -f "myfile.txt" ]; then
echo "File exists: myfile.txt"
else
echo "File does not exist: myfile.txt"
fi
# 获取文件信息
echo "File information:"
stat myfile.txt
# 修改文件权限
chmod 644 myfile.txt
echo "File permissions changed: myfile.txt"
# 重命名文件
mv myfile.txt mynewfile.txt
echo "File renamed to: mynewfile.txt"
# 删除文件
rm mynewfile.txt
echo "File deleted: mynewfile.txt"
# 查找文件
echo "Finding file:"
find . -name "mynewfile.txt"
1. 文件创建
使用touch
命令可以创建一个空文件,或者更新一个文件的修改时间。
touch filename.txt
2. 文件读取
使用cat
、less
、more
等命令可以读取文件内容。
cat filename.txt
使用while
循环逐行读取文件
while IFS= read -r line; do
echo "$line"
done < "filename.txt"
3. 文件写入
使用重定向符号>
可以将输出写入文件,使用>>
可以追加写入文件。
echo "Hello, World!" > filename.txt # 覆盖写入
echo "Hello, again!" >> filename.txt # 追加写入
使用tee
命令可以同时将输出写入文件和显示在终端
echo "Hello, World!" | tee filename.txt
echo "Hello, again!" | tee -a filename.txt
4. 文件删除
使用rm
命令可以删除文件。
rm filename.txt
5. 文件重命名和移动
使用mv
命令可以重命名或移动文件。
mv oldname.txt newname.txt # 重命名
mv filename.txt /path/to/directory/ # 移动
6. 检查文件是否存在
使用条件判断可以检查文件是否存在。
if [ -f "filename.txt" ]; then
echo "File exists."
else
echo "File does not exist."
fi
7. 获取文件信息
使用stat
命令可以获取文件的详细信息。
stat filename.txt
使用ls -l
命令可以获取文件的权限、大小等信息。
ls -l filename.txt
8. 文件权限操作
使用chmod
命令可以修改文件权限。
chmod 644 filename.txt # 设定文件权限
9. 文件查找
使用find
命令可以查找文件。
find /path/to/directory -name "filename.txt"
八、脚本调试
1. 使用 -x
选项启用调试模式
在脚本的开头添加-x
选项,脚本在执行时会显示每个命令及其参数的展开结果。
#!/bin/bash -x
echo "This is a debug mode example"
var="Hello"
echo $var
也可以在运行脚本时启用调试模式:
bash -x script.sh
2. 使用 set
命令控制调试选项
set
命令提供了多种选项来控制脚本的调试行为:
set -x # 启用调试模式,显示每个命令
set +x # 关闭调试模式
set -e # 启用脚本遇到错误时立即退出
set +e # 关闭错误检测,脚本继续执行
set -v # 启用显示脚本中的每一行
set +v # 关闭显示每一行
set -u # 启用未定义变量检查
set +u # 关闭未定义变量检查
示例如下:
#!/bin/bash
set -x
echo "Debugging with set -x"
var="Hello"
echo $var
set +x
3. 使用 trap
命令捕获错误和调试信息
trap
命令可以用于捕获脚本中的错误或特定事件,并执行相应的操作。
#!/bin/bash
trap 'echo "Error occurred at line $LINENO"; exit 1;' ERR
echo "This is a trap example"
echo $undefined_var # 这会引发错误
在这个示例中,trap
捕获 ERR
信号,并在发生错误时输出错误信息和行号,然后退出脚本。
4. 使用 echo
和 printf
调试变量
echo
和 printf
可以用于输出变量的值和调试信息。
#!/bin/bash
var="Hello"
echo "The value of var is: $var"
# 更精确的输出
printf "The value of var is: %s\n" "$var"
5. 使用 debug
函数进行调试
定义一个调试函数来简化调试信息的输出。
debug() {
echo "Debug: $1"
}
var="Hello"
debug "The value of var is $var"
6. 使用 return
和 exit
调试
在函数中使用return
,在脚本中使用exit
来调试错误和退出。
#!/bin/bash
my_function() {
local var="Hello"
echo "Inside function: $var"
return 1 # 你可以根据条件设置返回值
}
my_function
if [ $? -ne 0 ]; then
echo "Function returned an error"
fi
7. 使用 bash -n
命令检查语法
bash -n
命令检查脚本的语法而不执行脚本。
bash -n script.sh
8. 使用 shellcheck
工具进行静态分析
ShellCheck
是一个静态分析工具,可以帮助你检查脚本中的常见错误和潜在问题。
shellcheck script.sh
9. 使用 PS4
变量自定义调试输出
PS4
变量定义了set -x
模式下每条命令的前缀。
#!/bin/bash
PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
set -x
echo "Debugging with PS4"
var="Hello"
echo $var
在这个示例中,PS4
变量设置了调试输出的前缀格式,包括文件名、行号和函数名。
九、常用脚本
1. 自动备份脚本
将指定目录的文件备份到另一个目录中,并按日期命名备份文件夹。
#!/bin/bash
# 定义备份源目录和目标目录
SOURCE_DIR="/path/to/source"
BACKUP_DIR="/path/to/backup"
DATE=$(date +'%Y-%m-%d')
# 创建备份文件夹
BACKUP_PATH="$BACKUP_DIR/backup-$DATE"
mkdir -p "$BACKUP_PATH"
# 复制文件
cp -r "$SOURCE_DIR"/* "$BACKUP_PATH"
# 打印备份完成信息
echo "Backup completed at $BACKUP_PATH"
2. 检查磁盘空间
检查磁盘空间并发送警报邮件。
#!/bin/bash
# 磁盘空间警报阈值(百分比)
THRESHOLD=80
# 检查磁盘使用情况
USAGE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
if [ $USAGE -gt $THRESHOLD ]; then
echo "Disk space is above threshold: ${USAGE}%" | mail -s "Disk Space Alert" your_email@example.com
fi
3. 自动更新系统
自动更新系统的软件包并进行升级。
#!/bin/bash
# 更新软件包列表
sudo apt update
# 升级软件包
sudo apt upgrade -y
# 清理不需要的包
sudo apt autoremove -y
echo "System update completed."
4. 批量重命名文件
将指定目录下的所有.txt
文件扩展名改为.md
。
#!/bin/bash
# 指定目录
TARGET_DIR="/path/to/directory"
# 循环遍历所有.txt文件并重命名
for file in "$TARGET_DIR"/*.txt; do
mv "$file" "${file%.txt}.md"
done
echo "Renaming completed."
5. 生成随机密码
生成一个指定长度的随机密码。
#!/bin/bash
# 生成随机密码的长度
LENGTH=12
# 生成密码
PASSWORD=$(openssl rand -base64 12)
echo "Your new password is: $PASSWORD"
6. 监控进程状态
监控特定进程是否在运行,如果没有运行则发送警报。
#!/bin/bash
# 需要监控的进程名
PROCESS_NAME="nginx"
# 检查进程是否在运行
if pgrep $PROCESS_NAME > /dev/null
then
echo "$PROCESS_NAME is running."
else
echo "$PROCESS_NAME is not running!" | mail -s "$PROCESS_NAME Process Alert" your_email@example.com
fi
7. 定时任务脚本
使用cron
定时执行指定的脚本任务。
#!/bin/bash
# 定义任务内容
echo "Running scheduled task at $(date)" >> /path/to/logfile.log
配置cron
定时任务,每天午夜运行脚本:
0 0 * * * /path/to/this_script.sh
8. 检查网络连接
检查主机是否可以连接到指定的IP地址或域名。
#!/bin/bash
# 目标地址
TARGET="google.com"
# 检查网络连接
if ping -c 1 $TARGET &> /dev/null
then
echo "$TARGET is reachable."
else
echo "$TARGET is not reachable!" | mail -s "Network Alert" your_email@example.com
fi
9. 备份 MySQL 数据库
备份MySQL数据库到指定的目录中。
#!/bin/bash
# MySQL数据库连接信息
USER="your_user"
PASSWORD="your_password"
DATABASE="your_database"
BACKUP_DIR="/path/to/backup"
DATE=$(date +'%Y-%m-%d')
BACKUP_FILE="$BACKUP_DIR/${DATABASE}_backup_$DATE.sql"
# 执行备份
mysqldump -u $USER -p$PASSWORD $DATABASE > $BACKUP_FILE
# 打印备份完成信息
echo "Database backup completed at $BACKUP_FILE"
10. 从文件中提取数据
从CSV文件中提取特定列的数据。
#!/bin/bash
# CSV文件路径
CSV_FILE="/path/to/file.csv"
# 提取第二列数据
awk -F, '{ print $2 }' $CSV_FILE
11. 执行远程命令
使用SSH连接到远程主机并执行命令。
#!/bin/bash
# 远程主机信息
REMOTE_USER="user"
REMOTE_HOST="hostname_or_ip"
COMMAND="ls -l"
# 执行远程命令
ssh $REMOTE_USER@$REMOTE_HOST "$COMMAND"
12. 查找文件中的文本
在指定目录中查找包含特定文本的文件。
#!/bin/bash
# 查找目录
SEARCH_DIR="/path/to/directory"
# 查找的文本
SEARCH_TEXT="search_term"
# 查找文件
grep -rl "$SEARCH_TEXT" "$SEARCH_DIR"
13. 记录脚本执行时间
记录脚本的开始时间和结束时间。
#!/bin/bash
# 记录开始时间
START_TIME=$(date +'%Y-%m-%d %H:%M:%S')
echo "Script started at $START_TIME"
# 脚本的主要内容
sleep 5 # 模拟任务执行
# 记录结束时间
END_TIME=$(date +'%Y-%m-%d %H:%M:%S')
echo "Script ended at $END_TIME"
14. 自动清理临时文件
删除指定目录中的临时文件(例如*.tmp
文件)。
#!/bin/bash
# 临时文件目录
TEMP_DIR="/path/to/temp"
# 删除临时文件
rm -f "$TEMP_DIR"/*.tmp
echo "Temporary files cleaned up."
15. 文件夹存在判断并创建
ls build 2>/dev/null || mkdir -p build