Linux bash命令
什么是bash?
Bash(Bourne Again Shell)是Linux和Unix系统中最流行的shell,是Bourne shell的增强版。它不仅是命令解释器,还是一种强大的脚本编程语言。
Bash的历史: Bash由Brian Fox在1989年为GNU项目创建,作为Bourne shell的自由软件替代品。现在它是大多数Linux发行版的默认shell。
Bash的主要特性:
- 命令历史记录和命令补全
- 变量、数组和算术运算
- 条件判断和循环控制
- 函数定义和调用
- 输入/输出重定向和管道
- 作业控制和信号处理
- 正则表达式匹配
- 进程替换和协程
常见的Shell类型
Bash (Bourne Again Shell)
sh (Bourne Shell)
zsh (Z Shell)
ksh (Korn Shell)
csh (C Shell)
tcsh (TC Shell)
dash (Debian Almquist Shell)
fish (Friendly Interactive Shell)
语法格式
# 执行bash命令
bash [选项] [脚本文件] [参数...]
# 执行bash脚本
bash script.sh
./script.sh # 需要有执行权限
常用选项
| 选项 |
描述 |
示例 |
| -c |
从字符串执行命令 |
bash -c "echo Hello" |
| -x |
显示执行的命令(调试模式) |
bash -x script.sh |
| -n |
检查语法但不执行 |
bash -n script.sh |
| -v |
显示读取的命令(详细模式) |
bash -v script.sh |
| -e |
遇到错误立即退出 |
bash -e script.sh |
| -u |
使用未定义的变量时报错 |
bash -u script.sh |
| -o pipefail |
管道中任一命令失败则失败 |
set -o pipefail |
| --version |
显示bash版本 |
bash --version |
第一个Bash脚本
创建第一个Bash脚本:
#!/bin/bash
# 这是注释
echo "Hello, World!"
echo "当前目录: $(pwd)"
echo "用户: $USER"
echo "今天是: $(date)"
# 1. 创建脚本文件
nano hello.sh
# 2. 添加执行权限
chmod +x hello.sh
# 3. 运行脚本
./hello.sh
# 或
bash hello.sh
变量
1. 变量定义和使用:
# 定义变量(等号两边不能有空格)
name="Alice"
age=25
path="/home/user"
# 使用变量
echo $name
echo ${name} # 推荐使用{}明确变量边界
echo "My name is $name"
# 重新赋值
name="Bob"
echo "New name: $name"
2. 特殊变量:
#!/bin/bash
echo "脚本名: $0"
echo "参数个数: $#"
echo "所有参数: $@"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "进程ID: $$"
echo "上一个命令的退出状态: $?"
# 示例调用: ./script.sh arg1 arg2
3. 环境变量:
# 查看所有环境变量
printenv
env
# 常见环境变量
echo "PATH: $PATH"
echo "HOME: $HOME"
echo "USER: $USER"
echo "SHELL: $SHELL"
echo "PWD: $PWD"
# 设置环境变量
export MY_VAR="value"
export PATH="$PATH:/custom/path"
4. 只读变量:
# 定义只读变量
readonly PI=3.14159
readonly SCRIPT_NAME="myapp"
# 尝试修改会报错
PI=3.14 # bash: PI: readonly variable
字符串操作
1. 字符串基本操作:
str="Hello World"
# 获取长度
echo ${#str} # 11
# 提取子字符串
echo ${str:0:5} # Hello
echo ${str:6} # World
# 大小写转换
echo ${str^^} # HELLO WORLD (大写)
echo ${str,,} # hello world (小写)
2. 字符串替换和删除:
str="hello world hello"
# 替换第一个匹配
echo ${str/hello/HI} # HI world hello
# 替换所有匹配
echo ${str//hello/HI} # HI world HI
# 从开头删除匹配
path="/usr/local/bin"
echo ${path#/usr} # /local/bin
# 从结尾删除匹配
file="archive.tar.gz"
echo ${file%.gz} # archive.tar
echo ${file%%.tar.gz} # archive
数组
1. 索引数组:
# 定义数组
fruits=("apple" "banana" "orange")
# 或者
fruits[0]="apple"
fruits[1]="banana"
fruits[2]="orange"
# 访问元素
echo ${fruits[0]} # apple
echo ${fruits[1]} # banana
# 所有元素
echo ${fruits[@]} # apple banana orange
echo ${fruits[*]}
# 数组长度
echo ${#fruits[@]} # 3
echo ${#fruits[0]} # 5 (第一个元素的长度)
2. 关联数组(Bash 4+):
# 声明关联数组
declare -A person
person[name]="Alice"
person[age]=25
person[city]="New York"
# 访问
echo ${person[name]} # Alice
echo ${person[age]} # 25
# 所有键
echo ${!person[@]} # name age city
# 所有值
echo ${person[@]} # Alice 25 New York
3. 数组操作:
colors=("red" "green" "blue")
# 添加元素
colors+=("yellow")
echo ${colors[@]} # red green blue yellow
# 删除元素
unset colors[1]
echo ${colors[@]} # red blue yellow
# 切片
numbers=(1 2 3 4 5 6 7 8 9 10)
echo ${numbers[@]:2:5} # 3 4 5 6 7
算术运算
1. 基本算术运算:
# 使用 $(())
a=10
b=3
echo $((a + b)) # 13
echo $((a - b)) # 7
echo $((a * b)) # 30
echo $((a / b)) # 3 (整数除法)
echo $((a % b)) # 1
echo $((a ** b)) # 1000 (幂运算)
echo $((a++)) # 10 (后自增)
echo $((++a)) # 12 (前自增)
# 使用 $[]
echo $[a + b] # 15
echo $[a * b] # 36
2. 浮点运算(需要bc命令):
# 使用bc进行浮点运算
echo "scale=2; 10 / 3" | bc # 3.33
echo "3.14 + 2.72" | bc # 5.86
echo "sqrt(16)" | bc # 4
echo "2 ^ 8" | bc # 256
# 变量运算
a=10.5
b=3.2
echo "$a + $b" | bc # 13.7
3. 位运算:
a=5 # 二进制 0101
b=3 # 二进制 0011
echo $((a & b)) # 1 (按位与: 0001)
echo $((a | b)) # 7 (按位或: 0111)
echo $((a ^ b)) # 6 (按位异或: 0110)
echo $((~a)) # -6 (按位取反)
echo $((a << 1)) # 10 (左移: 1010)
echo $((a >> 1)) # 2 (右移: 0010)
条件判断
1. if语句:
#!/bin/bash
# 基本if语句
if [ condition ]; then
commands
fi
# if-else
if [ condition ]; then
commands1
else
commands2
fi
# if-elif-else
if [ condition1 ]; then
commands1
elif [ condition2 ]; then
commands2
else
commands3
fi
2. 测试条件:
# 字符串比较
[ "$a" = "$b" ] # 等于
[ "$a" != "$b" ] # 不等于
[ -z "$a" ] # 为空
[ -n "$a" ] # 不为空
[ "$a" < "$b" ] # 小于(按字典序)
[ "$a" > "$b" ] # 大于
# 数值比较
[ $a -eq $b ] # 等于
[ $a -ne $b ] # 不等于
[ $a -lt $b ] # 小于
[ $a -le $b ] # 小于等于
[ $a -gt $b ] # 大于
[ $a -ge $b ] # 大于等于
# 文件测试
[ -e file ] # 存在
[ -f file ] # 是普通文件
[ -d file ] # 是目录
[ -r file ] # 可读
[ -w file ] # 可写
[ -x file ] # 可执行
[ -s file ] # 非空
[ -L file ] # 是符号链接
3. 逻辑操作:
# 与
[ condition1 ] && [ condition2 ]
[ condition1 -a condition2 ]
# 或
[ condition1 ] || [ condition2 ]
[ condition1 -o condition2 ]
# 非
[ ! condition ]
# 组合示例
if [ -f "$file" ] && [ -r "$file" ]; then
echo "文件存在且可读"
fi
if [ $age -ge 18 ] || [ $parent_consent = "yes" ]; then
echo "允许访问"
fi
4. case语句:
#!/bin/bash
read -p "输入颜色: " color
case $color in
red)
echo "你选择了红色"
;;
green|blue)
echo "你选择了绿色或蓝色"
;;
yellow)
echo "你选择了黄色"
;;
*)
echo "未知颜色"
;;
esac
循环
1. for循环:
# 遍历列表
for fruit in apple banana orange; do
echo "水果: $fruit"
done
# 遍历数组
colors=("red" "green" "blue")
for color in "${colors[@]}"; do
echo "颜色: $color"
done
# 数字范围
for i in {1..5}; do
echo "数字: $i"
done
# 使用seq
for i in $(seq 1 2 10); do
echo "i: $i" # 1 3 5 7 9
done
# C风格for循环
for ((i=0; i<5; i++)); do
echo "计数: $i"
done
2. while循环:
# 基本while循环
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
count=$((count + 1))
done
# 无限循环
while true; do
echo "按Ctrl+C退出"
sleep 1
done
# 读取文件
while IFS= read -r line; do
echo "行: $line"
done < file.txt
# 读取命令输出
while read -r user terminal; do
echo "用户 $user 在 $terminal 登录"
done < <(who)
3. until循环:
# until循环(与while条件相反)
count=1
until [ $count -gt 5 ]; do
echo "计数: $count"
count=$((count + 1))
done
# 等待条件
until ping -c1 google.com &>/dev/null; do
echo "等待网络连接..."
sleep 5
done
echo "网络已连接"
4. 循环控制:
# break - 跳出循环
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo "i: $i"
done
# continue - 跳过本次循环
for i in {1..5}; do
if [ $i -eq 3 ]; then
continue
fi
echo "i: $i"
done
# break N - 跳出N层循环
for i in {1..3}; do
for j in {1..3}; do
if [ $j -eq 2 ]; then
break 2
fi
echo "i=$i, j=$j"
done
done
函数
1. 函数定义和调用:
# 函数定义
say_hello() {
echo "Hello, $1!"
return 0
}
# 调用函数
say_hello "Alice"
# 获取返回值
say_hello "Bob"
echo "返回值: $?" # 0
# 带返回值的函数
add() {
local sum=$(( $1 + $2 ))
echo $sum
}
result=$(add 10 20)
echo "结果: $result" # 30
2. 局部变量:
#!/bin/bash
global_var="我是全局变量"
myfunc() {
local local_var="我是局部变量"
echo "函数内: $local_var"
echo "函数内: $global_var"
}
myfunc
echo "函数外: $local_var" # 空
echo "函数外: $global_var" # 我是全局变量
3. 函数参数:
#!/bin/bash
# 函数参数
show_args() {
echo "参数个数: $#"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "所有参数: $@"
# 遍历所有参数
for arg in "$@"; do
echo "参数: $arg"
done
}
show_args "Alice" "Bob" "Charlie"
输入输出
1. 读取输入:
# 读取单行输入
read -p "请输入姓名: " name
echo "你好, $name!"
# 读取密码(不显示)
read -sp "请输入密码: " password
echo
echo "密码已接收"
# 读取数组
echo "输入多个值(空格分隔):"
read -a array
echo "数组: ${array[@]}"
# 读取到超时
if read -t 5 -p "5秒内输入: " input; then
echo "你输入了: $input"
else
echo "超时!"
fi
2. 重定向:
# 输出重定向
echo "内容" > file.txt # 覆盖
echo "更多内容" >> file.txt # 追加
# 输入重定向
wc -l < file.txt
# 错误重定向
command 2> error.log
command 2>> error.log
command 2>/dev/null # 丢弃错误
# 全部重定向
command &> output.log
command &>> output.log
command > output.log 2>&1
# Here Document
cat << EOF
这是一个Here文档
可以包含多行文本
EOF
# Here String
grep "pattern" <<< "这是一个字符串"
3. 管道:
# 基本管道
ls -l | grep "\.txt$" | wc -l
# 进程替换
diff <(ls dir1) <(ls dir2)
# tee命令
ls -l | tee files.txt | wc -l
ls -l | tee -a files.txt # 追加
脚本调试
1. 调试选项:
#!/bin/bash
# 在脚本中设置调试选项
set -e # 遇到错误退出
set -u # 使用未定义变量时报错
set -x # 显示执行的命令
set -o pipefail # 管道失败时退出
# 或者运行脚本时指定
bash -xe script.sh
# 临时启用调试
set -x
# 调试代码
set +x
2. 调试技巧:
#!/bin/bash
# 1. 输出变量值
echo "DEBUG: 变量值: $var"
# 2. 使用trap调试
trap 'echo "在行: $LINENO, 变量: $var"' DEBUG
# 3. 函数调用跟踪
trap 'echo "调用函数: ${FUNCNAME[0]}"' DEBUG
# 4. 记录执行时间
start_time=$(date +%s)
# 执行代码
end_time=$(date +%s)
echo "执行时间: $((end_time - start_time))秒"
实用脚本示例
1. 备份脚本:
#!/bin/bash
# 备份脚本
set -e
# 配置
BACKUP_DIR="/backup"
SOURCE_DIR="/var/www"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"
# 检查目录
[ -d "$BACKUP_DIR" ] || mkdir -p "$BACKUP_DIR"
[ -d "$SOURCE_DIR" ] || { echo "源目录不存在"; exit 1; }
# 创建备份
echo "开始备份..."
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
# 检查备份
if [ $? -eq 0 ] && [ -f "$BACKUP_FILE" ]; then
echo "备份成功: $BACKUP_FILE"
echo "文件大小: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
# 删除7天前的备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "清理7天前的备份完成"
2. 系统监控脚本:
#!/bin/bash
# 系统监控脚本
set -e
LOG_FILE="/var/log/system_monitor.log"
THRESHOLD_CPU=80
THRESHOLD_MEM=80
THRESHOLD_DISK=85
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# 检查CPU使用率
check_cpu() {
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if (( $(echo "$cpu_usage > $THRESHOLD_CPU" | bc -l) )); then
log_message "警告: CPU使用率过高: ${cpu_usage}%"
return 1
fi
log_message "CPU使用率正常: ${cpu_usage}%"
return 0
}
# 检查内存使用率
check_memory() {
local mem_total=$(free | grep Mem | awk '{print $2}')
local mem_used=$(free | grep Mem | awk '{print $3}')
local mem_usage=$((mem_used * 100 / mem_total))
if [ $mem_usage -gt $THRESHOLD_MEM ]; then
log_message "警告: 内存使用率过高: ${mem_usage}%"
return 1
fi
log_message "内存使用率正常: ${mem_usage}%"
return 0
}
# 检查磁盘使用率
check_disk() {
df -h | grep -E "^/dev/" | while read line; do
local usage=$(echo $line | awk '{print $5}' | sed 's/%//')
local mount=$(echo $line | awk '{print $6}')
if [ $usage -gt $THRESHOLD_DISK ]; then
log_message "警告: 磁盘 $mount 使用率过高: ${usage}%"
else
log_message "磁盘 $mount 使用率正常: ${usage}%"
fi
done
}
# 主函数
main() {
log_message "开始系统监控检查"
check_cpu
check_memory
check_disk
log_message "系统监控检查完成"
}
main "$@"
3. 服务管理脚本:
#!/bin/bash
# 服务管理脚本
set -e
SERVICE_NAME="nginx"
CONFIG_FILE="/etc/nginx/nginx.conf"
PID_FILE="/var/run/nginx.pid"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志函数
log() {
echo -e "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 检查服务状态
check_status() {
if systemctl is-active --quiet "$SERVICE_NAME"; then
log "${GREEN}服务 $SERVICE_NAME 正在运行${NC}"
return 0
else
log "${RED}服务 $SERVICE_NAME 已停止${NC}"
return 1
fi
}
# 启动服务
start_service() {
log "正在启动 $SERVICE_NAME..."
if systemctl start "$SERVICE_NAME"; then
log "${GREEN}服务启动成功${NC}"
return 0
else
log "${RED}服务启动失败${NC}"
return 1
fi
}
# 停止服务
stop_service() {
log "正在停止 $SERVICE_NAME..."
if systemctl stop "$SERVICE_NAME"; then
log "${GREEN}服务停止成功${NC}"
return 0
else
log "${RED}服务停止失败${NC}"
return 1
fi
}
# 重启服务
restart_service() {
log "正在重启 $SERVICE_NAME..."
if systemctl restart "$SERVICE_NAME"; then
log "${GREEN}服务重启成功${NC}"
return 0
else
log "${RED}服务重启失败${NC}"
return 1
fi
}
# 重新加载配置
reload_service() {
log "正在重新加载配置..."
if systemctl reload "$SERVICE_NAME"; then
log "${GREEN}配置重新加载成功${NC}"
return 0
else
log "${RED}配置重新加载失败${NC}"
return 1
fi
}
# 显示使用帮助
show_help() {
cat << EOF
用法: $0 {start|stop|restart|reload|status|check-config}
选项:
start 启动服务
stop 停止服务
restart 重启服务
reload 重新加载配置
status 检查服务状态
check-config 检查配置文件语法
EOF
}
# 检查配置文件语法
check_config() {
if nginx -t -c "$CONFIG_FILE"; then
log "${GREEN}配置文件语法正确${NC}"
return 0
else
log "${RED}配置文件语法错误${NC}"
return 1
fi
}
# 主函数
main() {
case "$1" in
start)
start_service
;;
stop)
stop_service
;;
restart)
restart_service
;;
reload)
reload_service
;;
status)
check_status
;;
check-config)
check_config
;;
*)
show_help
exit 1
;;
esac
}
main "$@"
Bash脚本最佳实践
1. 脚本开头:
#!/bin/bash
# 脚本说明
# 作者: 你的名字
# 版本: 1.0
# 描述: 这个脚本的功能描述
set -euo pipefail # 安全选项
IFS=$'\n\t' # 设置内部字段分隔符
# 常量定义
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly VERSION="1.0.0"
readonly LOG_FILE="/var/log/${SCRIPT_NAME%.*}.log"
# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
2. 错误处理:
#!/bin/bash
# 错误处理函数
error_exit() {
echo "$1" >&2
exit 1
}
# 检查命令是否存在
check_command() {
if ! command -v "$1" &> /dev/null; then
error_exit "命令 $1 未找到,请先安装"
fi
}
# 检查文件是否存在
check_file() {
if [ ! -f "$1" ]; then
error_exit "文件 $1 不存在"
fi
}
# 检查目录是否存在
check_directory() {
if [ ! -d "$1" ]; then
error_exit "目录 $1 不存在"
fi
}
# 使用示例
check_command curl
check_file "/etc/passwd"
check_directory "/var/log"
3. 日志函数:
#!/bin/bash
# 日志函数
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case "$level" in
INFO)
echo -e "${GREEN}[INFO]${NC} $message"
;;
WARN)
echo -e "${YELLOW}[WARN]${NC} $message"
;;
ERROR)
echo -e "${RED}[ERROR]${NC} $message"
;;
DEBUG)
[ "${DEBUG:-false}" = "true" ] && echo -e "${BLUE}[DEBUG]${NC} $message"
;;
esac
# 写入日志文件
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
}
# 使用示例
log "INFO" "脚本开始执行"
log "WARN" "磁盘空间不足"
log "ERROR" "文件不存在"
重要安全提示:
1. 永远不要以root身份运行不受信任的脚本
2. 验证所有用户输入
3. 使用引号引用变量,避免单词分割和通配符扩展
4. 使用[[ ]]进行条件测试,比[ ]更安全
5. 在处理文件名时,使用--选项分隔选项和参数
6. 不要使用eval执行用户输入
7. 为脚本设置适当的权限(通常为755)