Bash脚本编程基础语法
1. Bash脚本的基本结构
Bash脚本是由一系列的Bash命令组成的文本文件。通常,一个Bash脚本以#!/bin/bash
开头,这一行被称为shebang,它告诉系统使用/bin/bash
来解释执行这个脚本。
例如,创建一个简单的脚本hello.sh
:
#!/bin/bash
echo "Hello, World!"
在上述脚本中,#!/bin/bash
指定了使用Bash解释器,echo
命令用于输出文本到标准输出。
2. 变量
2.1 变量的定义
在Bash中,变量无需事先声明类型。定义变量的语法为:变量名=变量值
。注意,等号两边不能有空格。
name="John"
age=30
这里定义了一个字符串变量name
和一个数值变量age
。
2.2 变量的引用
引用变量时,在变量名前加$
符号。例如:
name="Alice"
echo "My name is $name"
上述代码会输出My name is Alice
。
2.3 环境变量
Bash中有许多预定义的环境变量,例如$PATH
,它包含了系统查找可执行文件的路径。可以使用export
命令将自定义变量提升为环境变量。
my_var="test"
export my_var
这样其他子进程就可以访问my_var
变量。
3. 数据类型
3.1 字符串
字符串是Bash中最常用的数据类型之一。可以使用单引号或双引号来定义字符串。
单引号定义的字符串,其中的变量不会被替换,双引号定义的字符串,其中的变量会被替换。
name="Bob"
str1='My name is $name'
str2="My name is $name"
echo $str1
echo $str2
上述代码输出结果为:
My name is $name
My name is Bob
3.2 数值
Bash支持整数运算。可以使用let
命令或((...))
结构进行数值计算。
a=5
b=3
let result=a+b
echo $result
((result=a*b))
echo $result
上述代码分别输出8
和15
。
4. 运算符
4.1 算术运算符
Bash支持常见的算术运算符,如+
、-
、*
、/
、%
(取模)等。除了前面提到的let
和((...))
,还可以使用expr
命令,但expr
要求运算符与操作数之间有空格。
a=10
b=3
result=$(expr $a + $b)
echo $result
4.2 比较运算符
用于比较数值或字符串。比较数值的运算符有-eq
(等于)、-ne
(不等于)、-gt
(大于)、-lt
(小于)、-ge
(大于等于)、-le
(小于等于)。
a=5
b=3
if [ $a -gt $b ]; then
echo "$a is greater than $b"
fi
比较字符串的运算符有=
(等于)、!=
(不等于)。
str1="hello"
str2="world"
if [ $str1 != $str2 ]; then
echo "Strings are not equal"
fi
4.3 逻辑运算符
逻辑与(&&
)、逻辑或(||
)和逻辑非(!
)。
a=5
b=3
if [ $a -gt 0 ] && [ $b -gt 0 ]; then
echo "Both a and b are positive"
fi
5. 控制结构
5.1 if语句
基本语法为:
if [ 条件判断 ]; then
命令序列1
else
命令序列2
fi
例如:
age=20
if [ $age -ge 18 ]; then
echo "You are an adult"
else
echo "You are a minor"
fi
5.2 case语句
用于多分支选择,语法如下:
case 变量 in
模式1)
命令序列1
;;
模式2)
命令序列2
;;
*)
命令序列3
;;
esac
示例:
fruit="apple"
case $fruit in
apple)
echo "It's an apple"
;;
banana)
echo "It's a banana"
;;
*)
echo "Unknown fruit"
;;
esac
5.3 for循环
有两种常见的形式,一种是传统的C风格,另一种是基于列表的。
C风格:
for (( i = 1; i <= 5; i++ )); do
echo $i
done
基于列表的:
fruits=("apple" "banana" "cherry")
for fruit in ${fruits[@]}; do
echo $fruit
done
5.4 while循环
只要条件为真,就会一直执行循环体。
count=1
while [ $count -le 5 ]; do
echo $count
((count++))
done
5.5 until循环
与while
相反,只要条件为假,就执行循环体。
count=1
until [ $count -gt 5 ]; do
echo $count
((count++))
done
6. 函数
6.1 函数定义
语法为:
函数名() {
命令序列
[return 返回值]
}
例如:
add() {
result=$(( $1 + $2 ))
echo $result
}
sum=$(add 3 5)
echo $sum
6.2 函数参数
在函数内部,可以通过$1
、$2
等变量来访问传递给函数的参数。$0
表示脚本本身的名称,$#
表示参数的个数,$*
和$@
都表示所有参数,但在处理带空格的参数时有细微差别。
print_args() {
echo "Number of arguments: $#"
echo "All arguments: $*"
echo "Each argument:"
for arg in "$@"; do
echo $arg
done
}
print_args one "two three" four
7. 文件操作
7.1 文件测试
可以使用[ -条件 文件名 ]
的形式来测试文件的属性。例如:
file="test.txt"
if [ -e $file ]; then
echo "File exists"
fi
常见的文件测试条件有:
-e
:文件或目录是否存在-f
:是否是普通文件-d
:是否是目录-r
:文件是否可读-w
:文件是否可写-x
:文件是否可执行
7.2 文件读取
可以使用read
命令从文件中读取内容。例如:
while read line; do
echo $line
done < test.txt
上述代码会逐行读取test.txt
的内容并输出。
7.3 文件写入
使用>
进行覆盖写入,>>
进行追加写入。
echo "This is a test" > test.txt
echo "Another line" >> test.txt
8. 输入输出重定向
8.1 标准输出重定向
将命令的输出结果重定向到文件。例如:
ls -l > file_list.txt
8.2 标准错误输出重定向
将命令的错误信息重定向到文件。例如:
ls non_existent_file 2> error.txt
8.3 同时重定向标准输出和标准错误输出
ls non_existent_file &> combined.txt
8.4 输入重定向
将文件内容作为命令的输入。例如:
wc -l < test.txt
9. 管道
管道(|
)用于将一个命令的输出作为另一个命令的输入。
ls -l | grep "txt"
上述代码先列出当前目录下的详细文件列表,然后通过管道将输出传递给grep
命令,筛选出文件名中包含txt
的行。
10. 数组
10.1 数组的定义
可以使用括号来定义数组。例如:
nums=(1 2 3 4 5)
也可以逐个赋值:
fruits[0]="apple"
fruits[1]="banana"
fruits[2]="cherry"
10.2 数组的访问
通过索引访问数组元素,索引从0开始。
nums=(1 2 3 4 5)
echo ${nums[0]}
获取数组所有元素:
fruits=("apple" "banana" "cherry")
echo ${fruits[@]}
获取数组长度:
nums=(1 2 3 4 5)
echo ${#nums[@]}
11. 特殊变量
11.1 $?
表示上一个命令的退出状态码。0表示命令成功执行,非0表示有错误发生。
ls
echo $?
11.2 $$
表示当前脚本的进程ID。
echo "My PID is $$"
11.3 $!
表示上一个在后台运行的进程ID。
sleep 10 &
echo "Background process ID: $!"
12. 调试Bash脚本
可以在脚本开头添加set -x
来开启调试模式,它会在执行每条命令前打印出该命令及其参数。调试结束后,可以使用set +x
关闭调试模式。
#!/bin/bash
set -x
a=5
b=3
result=$(( a + b ))
echo $result
set +x
此外,还可以使用bash -n
来检查脚本语法错误,而不实际执行脚本。
bash -n my_script.sh
13. 与其他程序交互
Bash脚本可以调用外部程序,并处理它们的输出。例如,调用curl
获取网页内容:
content=$(curl -s https://www.example.com)
echo $content
还可以调用Python脚本:
python_result=$(python -c "print(2 + 3)")
echo $python_result
14. 处理命令行参数
在Bash脚本中,可以通过$1
、$2
等变量来访问命令行参数。例如,创建一个脚本args.sh
:
#!/bin/bash
echo "First argument: $1"
echo "Second argument: $2"
运行脚本时传递参数:
./args.sh hello world
输出:
First argument: hello
Second argument: world
还可以使用getopts
来处理带选项的参数。例如:
#!/bin/bash
while getopts ":a:b:c" opt; do
case $opt in
a)
var_a=$OPTARG
;;
b)
var_b=$OPTARG
;;
c)
echo "Option -c was specified"
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
:)
echo "Option -$OPTARG requires an argument." >&2
;;
esac
done
shift $((OPTIND - 1))
echo "var_a: $var_a"
echo "var_b: $var_b"
echo "Remaining arguments: $*"
运行脚本:
./script.sh -a value1 -b value2 other_arg1 other_arg2
输出:
var_a: value1
var_b: value2
Remaining arguments: other_arg1 other_arg2
15. 脚本的执行权限
为了能够执行Bash脚本,需要赋予脚本可执行权限。可以使用chmod
命令来实现。
chmod +x my_script.sh
这样就可以直接通过./my_script.sh
来执行脚本了。如果脚本没有可执行权限,可能会收到Permission denied
的错误。
16. 信号处理
Bash脚本可以捕获和处理系统信号。使用trap
命令来设置信号处理函数。
例如,捕获SIGINT
(通常由Ctrl+C
产生)信号:
#!/bin/bash
trap 'echo "Caught SIGINT, exiting gracefully..."' SIGINT
while true; do
echo "Running..."
sleep 1
done
当用户在脚本运行时按下Ctrl+C
,脚本会捕获到SIGINT
信号并执行相应的处理函数,输出提示信息后退出。
常见的信号有:
SIGINT
:中断信号,通常由Ctrl+C
产生SIGTERM
:终止信号,通常由kill
命令发送SIGHUP
:挂起信号,通常在终端关闭时发送
17. 环境配置与脚本的可移植性
在编写Bash脚本时,考虑可移植性很重要。不同的系统可能使用不同版本的Bash,甚至可能使用其他Shell。
为了提高可移植性,可以:
- 使用标准的Shell语法:避免使用特定版本Bash的扩展语法。
- 检查环境变量:例如,检查
$PATH
是否包含必要的工具。 - 使用相对路径:尽量避免使用绝对路径,除非必要。
另外,在脚本开头使用#!/bin/sh
而不是#!/bin/bash
,/bin/sh
通常是一个POSIX兼容的Shell,能在更多系统上运行。但要注意,sh
的功能可能比bash
有限,需要根据实际需求选择。
18. 脚本的模块化与复用
对于大型的Bash脚本项目,将脚本模块化可以提高代码的可维护性和复用性。
可以将常用的功能封装成函数,放在一个单独的文件中,然后在其他脚本中通过source
命令引入。
例如,创建一个common_functions.sh
文件:
# common_functions.sh
add() {
result=$(( $1 + $2 ))
echo $result
}
subtract() {
result=$(( $1 - $2 ))
echo $result
}
然后在另一个脚本main.sh
中使用:
#!/bin/bash
source common_functions.sh
sum=$(add 3 5)
echo "Sum: $sum"
diff=$(subtract 5 3)
echo "Difference: $diff"
这样,不同的脚本可以复用common_functions.sh
中的函数,减少重复代码。
19. 日志记录
在Bash脚本中进行日志记录有助于调试和监控脚本的运行。可以使用echo
配合输出重定向将日志信息写入文件。
例如:
#!/bin/bash
log_file="script.log"
echo "$(date): Starting script" >> $log_file
# 脚本主体部分
echo "$(date): Finishing script" >> $log_file
为了使日志更具可读性,可以添加时间戳等信息。还可以根据脚本的运行状态记录不同级别的日志,例如:
#!/bin/bash
log_file="script.log"
log() {
local level=$1
local message=$2
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "$timestamp [$level] $message" >> $log_file
}
log "INFO" "Starting script"
# 脚本主体部分
log "ERROR" "An error occurred"
log "INFO" "Finishing script"
这样可以清晰地区分不同类型的日志信息。
20. 性能优化
对于复杂的Bash脚本,性能优化很重要。以下是一些优化技巧:
- 减少外部命令调用:外部命令调用会消耗系统资源和时间,尽量使用Bash内置命令。例如,使用
((...))
进行数值计算比调用expr
效率更高。 - 避免不必要的循环:如果可以通过一次性操作完成任务,就不要使用循环。
- 优化I/O操作:减少文件的读写次数,批量处理数据。
例如,假设要对一个文件中的所有数字求和,一种低效的方法是逐行读取并求和:
sum=0
while read num; do
((sum += num))
done < numbers.txt
echo $sum
一种更高效的方法是使用awk
命令一次性处理:
sum=$(awk '{s+=$1} END {print s}' numbers.txt)
echo $sum
通过合理运用这些优化技巧,可以显著提高Bash脚本的执行效率。