Shell脚本是Linux系统管理的利器,通过编写脚本可以自动化完成复杂的系统管理任务。本章将介绍Shell脚本的基础知识和编写规范。
Shell脚本:包含一系列命令的文本文件,按顺序执行。
Shell脚本特点:
东巴文理解:Shell脚本就像菜谱,按步骤执行一系列操作,自动完成复杂任务。
常见用途:
东巴文应用场景:
创建hello.sh脚本:
#!/bin/bash
# 第一个Shell脚本
# 作者:东巴文
# 日期:2024-01-01
echo "Hello, World!"
echo "欢迎使用东巴文Linux教程"
echo "当前用户:$USER"
echo "当前目录:$PWD"
echo "当前时间:$(date)"
脚本说明:
#!/bin/bash:Shebang,指定解释器#:注释符号echo:输出命令$USER、$PWD:环境变量$(date):命令替换东巴文提示:Shebang必须放在第一行,告诉系统使用哪个Shell解释器。
方法一:使用bash命令:
# 使用bash执行
bash hello.sh
# 输出:
# Hello, World!
# 欢迎使用东巴文Linux教程
# 当前用户:user
# 当前目录:/home/user
# 当前时间:2024年 01月 01日 星期一 10:00:00 CST
方法二:赋予执行权限:
# 赋予执行权限
chmod +x hello.sh
# 执行脚本
./hello.sh
# 输出:
# Hello, World!
# 欢迎使用东巴文Linux教程
# 当前用户:user
# 当前目录:/home/user
# 当前时间:2024年 01月 01日 星期一 10:00:00 CST
方法三:使用source命令:
# 使用source执行
source hello.sh
# 或使用点命令
. hello.sh
# 输出:
# Hello, World!
# 欢迎使用东巴文Linux教程
# 当前用户:user
# 当前目录:/home/user
# 当前时间:2024年 01月 01日 星期一 10:00:00 CST
东巴文区别:
bash:启动新Shell执行脚本./:启动新Shell执行脚本source:在当前Shell中执行脚本#!/bin/bash
#===============================================================================
# 脚本名称:script_name.sh
# 脚本功能:脚本功能描述
# 作者:东巴文
# 创建日期:2024-01-01
# 最后修改:2024-01-01
# 版本:1.0
# 使用方法:./script_name.sh [参数]
# 注意事项:注意事项说明
#===============================================================================
# 设置变量
SCRIPT_NAME=$(basename "$0")
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# 设置错误处理
set -e # 遇到错误立即退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中的错误也会导致脚本退出
# 定义函数
function print_info() {
echo "[INFO] $1"
}
function print_error() {
echo "[ERROR] $1" >&2
}
# 主函数
function main() {
print_info "脚本开始执行..."
# 脚本主要逻辑
print_info "当前目录:$PWD"
print_info "当前用户:$USER"
print_info "脚本执行完成"
}
# 执行主函数
main "$@"
东巴文最佳实践:使用标准模板,让脚本更规范、更易维护。
常见Shebang:
#!/bin/bash # 使用bash
#!/bin/sh # 使用sh
#!/usr/bin/env bash # 使用env查找bash
#!/usr/bin/env python # Python脚本
#!/usr/bin/env perl # Perl脚本
东巴文推荐:使用#!/usr/bin/env bash,更通用。
# 这是单行注释
echo "Hello" # 行尾注释
# 注释应该解释"为什么",而不是"是什么"
# 例如:
# 检查文件是否存在,避免重复下载
if [ ! -f "$FILE" ]; then
wget "$URL"
fi
方法一:使用多个单行注释:
# 这是多行注释的第一行
# 这是多行注释的第二行
# 这是多行注释的第三行
echo "Hello"
方法二:使用冒号:
: '
这是多行注释
可以写很多内容
不会被解释执行
'
echo "Hello"
方法三:使用Here Document:
: <<'EOF'
这是多行注释
可以写很多内容
不会被解释执行
EOF
echo "Hello"
东巴文提示:推荐使用方法一,更清晰易读。
#!/bin/bash
# 参数示例脚本
echo "脚本名称:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第三个参数:$3"
echo "参数个数:$#"
echo "所有参数:$@"
echo "所有参数:$*"
# 执行脚本
# ./params.sh a b c d
# 输出:
# 脚本名称:./params.sh
# 第一个参数:a
# 第二个参数:b
# 第三个参数:c
# 参数个数:4
# 所有参数:a b c d
# 所有参数:a b c d
特殊变量:
| 变量 | 说明 |
|---|---|
| $0 | 脚本名称 |
| 9 | 第1-9个参数 |
| ${10} | 第10个参数 |
| $# | 参数个数 |
| $@ | 所有参数(数组) |
| $* | 所有参数(字符串) |
| $? | 上个命令的退出状态 |
| $$ | 当前进程PID |
| $! | 后台进程PID |
检查参数:
#!/bin/bash
# 检查参数个数
if [ $# -lt 1 ]; then
echo "使用方法:$0 <参数1> [参数2]"
echo "示例:$0 hello world"
exit 1
fi
# 检查参数是否为空
if [ -z "$1" ]; then
echo "错误:参数不能为空"
exit 1
fi
echo "参数检查通过"
echo "第一个参数:$1"
参数默认值:
#!/bin/bash
# 设置默认值
NAME=${1:-"东巴文"} # 如果$1为空,使用"东巴文"
AGE=${2:-18} # 如果$2为空,使用18
echo "姓名:$NAME"
echo "年龄:$AGE"
# 执行脚本
# ./default.sh
# 输出:
# 姓名:东巴文
# 年龄:18
# ./default.sh 张三 25
# 输出:
# 姓名:张三
# 年龄:25
使用getopts处理选项:
#!/bin/bash
# 选项参数示例
# 显示帮助信息
function show_help() {
echo "使用方法:$0 [选项]"
echo "选项:"
echo " -h 显示帮助信息"
echo " -f 指定文件"
echo " -v 显示详细信息"
echo " -n 指定名称"
}
# 初始化变量
FILE=""
VERBOSE=false
NAME=""
# 解析选项
while getopts "hvf:n:" opt; do
case $opt in
h)
show_help
exit 0
;;
f)
FILE="$OPTARG"
;;
v)
VERBOSE=true
;;
n)
NAME="$OPTARG"
;;
\?)
echo "无效选项:-$OPTARG" >&2
exit 1
;;
:)
echo "选项 -$OPTARG 需要参数" >&2
exit 1
;;
esac
done
# 显示结果
echo "文件:$FILE"
echo "名称:$NAME"
echo "详细模式:$VERBOSE"
# 执行脚本
# ./options.sh -h
# ./options.sh -f test.txt -n "东巴文" -v
东巴文理解:getopts专门用于处理命令行选项,让脚本更专业。
命名规则:
正确示例:
# 正确的变量名
NAME="东巴文"
age=25
_user="user"
MAX_VALUE=100
错误示例:
# 错误的变量名
1name="error" # 不能以数字开头
my-name="error" # 不能包含减号
my name="error" # 不能包含空格
东巴文规范:
基本赋值:
# 赋值时等号两边不能有空格
name="东巴文"
# 错误示例(有空格)
name = "东巴文" # 错误!
# 多个变量赋值
a=1 b=2 c=3
# 使用命令结果赋值
current_date=$(date)
current_user=$USER
# 使用算术表达式赋值
count=$((5 + 3))
变量引用:
name="东巴文"
# 使用变量
echo $name
echo ${name}
# 变量拼接
echo "欢迎${name}使用Linux"
echo "欢迎$name使用Linux"
# 变量边界
echo "${name}is good" # 正确
echo "$nameis good" # 错误,找不到变量nameis
# 花括号的作用
file="test"
echo "${file}_name" # 输出:test_name
echo "$file_name" # 输出空,变量file_name不存在
东巴文提示:推荐使用${变量},更清晰,避免歧义。
字符串定义:
# 单引号字符串
str1='Hello World'
# 双引号字符串
str2="Hello World"
# 无引号字符串
str3=Hello
# 多行字符串
str4="第一行
第二行
第三行"
单引号vs双引号:
name="东巴文"
# 单引号:原样输出,不解析变量和命令
echo 'Hello $name'
# 输出:Hello $name
# 双引号:解析变量和命令
echo "Hello $name"
# 输出:Hello 东巴文
# 双引号中执行命令
echo "当前时间:$(date)"
# 输出:当前时间:2024年 01月 01日 星期一 10:00:00 CST
# 单引号中不能使用转义字符
echo 'Hello\nWorld'
# 输出:Hello\nWorld
# 双引号中可以使用转义字符
echo -e "Hello\nWorld"
# 输出:
# Hello
# World
东巴文理解:
字符串操作:
str="Hello World"
# 字符串长度
echo ${#str}
# 输出:11
# 提取子串
echo ${str:0:5} # 从第0个开始,提取5个字符
# 输出:Hello
echo ${str:6} # 从第6个开始到结尾
# 输出:World
# 查找子串
expr index "$str" "W"
# 输出:7(W的位置)
# 字符串替换
echo ${str/World/东巴文}
# 输出:Hello 东巴文
# 删除字符串
echo ${str#Hello } # 删除开头匹配的字符串
# 输出:World
echo ${str%World} # 删除结尾匹配的字符串
# 输出:Hello
# 大小写转换
echo ${str^^} # 转大写
# 输出:HELLO WORLD
echo ${str,,} # 转小写
# 输出:hello world
整数定义:
# 十进制
num1=10
# 八进制(以0开头)
num2=012
# 十六进制(以0x开头)
num3=0xFF
数值运算:
# 使用$(())
a=$((5 + 3))
echo $a
# 输出:8
# 使用$[]
b=$[5 + 3]
echo $b
# 输出:8
# 使用expr
c=$(expr 5 + 3)
echo $c
# 输出:8
# 使用let
let d=5+3
echo $d
# 输出:8
# 使用bc(支持浮点数)
e=$(echo "5.5 + 3.3" | bc)
echo $e
# 输出:8.8
东巴文推荐:使用$(()),简洁高效。
运算符:
# 算术运算符
echo $((5 + 3)) # 加法:8
echo $((5 - 3)) # 减法:2
echo $((5 * 3)) # 乘法:15
echo $((5 / 3)) # 除法:1
echo $((5 % 3)) # 取余:2
echo $((5 ** 3)) # 幂运算:125
# 自增自减
a=5
echo $((a++)) # 输出5,然后a变为6
echo $((a--)) # 输出6,然后a变为5
echo $((++a)) # a先变为6,然后输出6
echo $((--a)) # a先变为5,然后输出5
# 位运算
echo $((5 & 3)) # 按位与:1
echo $((5 | 3)) # 按位或:7
echo $((5 ^ 3)) # 按位异或:6
echo $((~5)) # 按位取反:-6
echo $((5 << 1)) # 左移:10
echo $((5 >> 1)) # 右移:2
数组定义:
# 方式一:括号定义
arr1=(1 2 3 4 5)
# 方式二:逐个定义
arr2[0]=1
arr2[1]=2
arr2[2]=3
# 方式三:定义特定元素
arr3=([0]=1 [2]=3 [4]=5)
# 字符串数组
names=("东巴文" "张三" "李四" "王五")
数组操作:
arr=(1 2 3 4 5)
# 获取数组长度
echo ${#arr[@]}
# 输出:5
echo ${#arr[*]}
# 输出:5
# 获取单个元素
echo ${arr[0]}
# 输出:1
# 获取所有元素
echo ${arr[@]}
# 输出:1 2 3 4 5
echo ${arr[*]}
# 输出:1 2 3 4 5
# 获取数组索引
echo ${!arr[@]}
# 输出:0 1 2 3 4
# 数组切片
echo ${arr[@]:1:3}
# 输出:2 3 4
# 添加元素
arr+=(6 7)
echo ${arr[@]}
# 输出:1 2 3 4 5 6 7
# 删除元素
unset arr[2]
echo ${arr[@]}
# 输出:1 2 4 5 6 7
# 遍历数组
for i in "${arr[@]}"; do
echo $i
done
东巴文提示:数组索引从0开始,使用${数组[@]}获取所有元素。
常见环境变量:
echo $HOME # 用户主目录
echo $USER # 当前用户名
echo $PWD # 当前目录
echo $PATH # 命令搜索路径
echo $SHELL # 当前Shell
echo $LANG # 语言设置
echo $HOSTNAME # 主机名
echo $RANDOM # 随机数
echo $UID # 用户ID
echo $GROUPS # 用户所属组
设置环境变量:
# 临时设置
export MY_VAR="东巴文"
# 永久设置(写入~/.bashrc)
echo 'export MY_VAR="东巴文"' >> ~/.bashrc
source ~/.bashrc
# 删除环境变量
unset MY_VAR
#!/bin/bash
echo "脚本名称:$0"
echo "进程ID:$$"
echo "参数个数:$#"
echo "所有参数:$@"
echo "所有参数:$*"
# 上个命令的退出状态
ls /nonexistent
echo "退出状态:$?"
# 输出:退出状态:2
# 后台进程ID
sleep 10 &
echo "后台进程ID:$!"
*的区别:
#!/bin/bash
# 区别演示脚本
echo "使用\$@:"
for arg in "$@"; do
echo "参数:$arg"
done
echo "使用\$*:"
for arg in "$*"; do
echo "参数:$arg"
done
# 执行脚本
# ./test.sh "a b" c d
# 输出:
# 使用$@:
# 参数:a b
# 参数:c
# 参数:d
# 使用$*:
# 参数:a b c d
东巴文理解:
$@:保留参数边界$*:合并所有参数为一个字符串#!/bin/bash
a=10
b=3
# 加法
echo "a + b = $((a + b))"
# 输出:a + b = 13
# 减法
echo "a - b = $((a - b))"
# 输出:a - b = 7
# 乘法
echo "a * b = $((a * b))"
# 输出:a * b = 30
# 除法
echo "a / b = $((a / b))"
# 输出:a / b = 3
# 取余
echo "a % b = $((a % b))"
# 输出:a % b = 1
# 幂运算
echo "a ** b = $((a ** b))"
# 输出:a ** b = 1000
#!/bin/bash
a=10
# 简单赋值
a=10
echo "a = $a"
# 输出:a = 10
# 加等于
((a += 5))
echo "a += 5, a = $a"
# 输出:a += 5, a = 15
# 减等于
((a -= 3))
echo "a -= 3, a = $a"
# 输出:a -= 3, a = 12
# 乘等于
((a *= 2))
echo "a *= 2, a = $a"
# 输出:a *= 2, a = 24
# 除等于
((a /= 4))
echo "a /= 4, a = $a"
# 输出:a /= 4, a = 6
# 取余等于
((a %= 4))
echo "a %= 4, a = $a"
# 输出:a %= 4, a = 2
#!/bin/bash
a=10
b=20
# 等于
if [ $a -eq $b ]; then
echo "$a 等于 $b"
else
echo "$a 不等于 $b"
fi
# 输出:10 不等于 20
# 不等于
if [ $a -ne $b ]; then
echo "$a 不等于 $b"
fi
# 输出:10 不等于 20
# 大于
if [ $a -gt $b ]; then
echo "$a 大于 $b"
else
echo "$a 不大于 $b"
fi
# 输出:10 不大于 20
# 小于
if [ $a -lt $b ]; then
echo "$a 小于 $b"
fi
# 输出:10 小于 20
# 大于等于
if [ $a -ge $b ]; then
echo "$a 大于等于 $b"
else
echo "$a 小于 $b"
fi
# 输出:10 小于 20
# 小于等于
if [ $a -le $b ]; then
echo "$a 小于等于 $b"
fi
# 输出:10 小于等于 20
数值比较运算符:
| 运算符 | 说明 | 英文 |
|---|---|---|
| -eq | 等于 | equal |
| -ne | 不等于 | not equal |
| -gt | 大于 | greater than |
| -lt | 小于 | less than |
| -ge | 大于等于 | greater or equal |
| -le | 小于等于 | less or equal |
#!/bin/bash
str1="hello"
str2="world"
str3="hello"
# 等于
if [ "$str1" = "$str3" ]; then
echo "str1 等于 str3"
fi
# 输出:str1 等于 str3
# 不等于
if [ "$str1" != "$str2" ]; then
echo "str1 不等于 str2"
fi
# 输出:str1 不等于 str2
# 字符串长度为0
empty=""
if [ -z "$empty" ]; then
echo "empty是空字符串"
fi
# 输出:empty是空字符串
# 字符串长度不为0
if [ -n "$str1" ]; then
echo "str1不是空字符串"
fi
# 输出:str1不是空字符串
# 字符串是否为空
if [ "$str1" ]; then
echo "str1不为空"
fi
# 输出:str1不为空
字符串比较运算符:
| 运算符 | 说明 |
|---|---|
| = | 字符串相等 |
| != | 字符串不相等 |
| -z | 字符串长度为0 |
| -n | 字符串长度不为0 |
东巴文提示:字符串比较时,变量要用双引号括起来,避免空格导致错误。
#!/bin/bash
a=10
b=20
# 逻辑与
if [ $a -gt 5 ] && [ $b -gt 15 ]; then
echo "两个条件都成立"
fi
# 输出:两个条件都成立
# 逻辑或
if [ $a -gt 15 ] || [ $b -gt 15 ]; then
echo "至少一个条件成立"
fi
# 输出:至少一个条件成立
# 逻辑非
if [ ! $a -gt 15 ]; then
echo "a不大于15"
fi
# 输出:a不大于15
# 使用[[ ]]支持&&和||
if [[ $a -gt 5 && $b -gt 15 ]]; then
echo "两个条件都成立"
fi
# 输出:两个条件都成立
# 使用(( ))支持C风格语法
if (( a > 5 && b > 15 )); then
echo "两个条件都成立"
fi
# 输出:两个条件都成立
逻辑运算符:
| 运算符 | 说明 |
|---|---|
| && | 逻辑与 |
| || | 逻辑或 |
| ! | 逻辑非 |
#!/bin/bash
file="/etc/passwd"
dir="/home"
# 文件是否存在
if [ -e "$file" ]; then
echo "$file 存在"
fi
# 输出:/etc/passwd 存在
# 是否为普通文件
if [ -f "$file" ]; then
echo "$file 是普通文件"
fi
# 输出:/etc/passwd 是普通文件
# 是否为目录
if [ -d "$dir" ]; then
echo "$dir 是目录"
fi
# 输出:/home 是目录
# 是否为块设备文件
if [ -b "/dev/sda" ]; then
echo "/dev/sda 是块设备"
fi
# 是否为字符设备文件
if [ -c "/dev/tty" ]; then
echo "/dev/tty 是字符设备"
fi
# 是否为符号链接
if [ -L "/bin/sh" ]; then
echo "/bin/sh 是符号链接"
fi
# 是否为管道文件
if [ -p "/tmp/pipe" ]; then
echo "/tmp/pipe 是管道文件"
fi
# 是否为套接字文件
if [ -S "/tmp/sock" ]; then
echo "/tmp/sock 是套接字文件"
fi
#!/bin/bash
file="/etc/passwd"
# 是否可读
if [ -r "$file" ]; then
echo "$file 可读"
fi
# 输出:/etc/passwd 可读
# 是否可写
if [ -w "$file" ]; then
echo "$file 可写"
else
echo "$file 不可写"
fi
# 输出:/etc/passwd 不可写(普通用户)
# 是否可执行
if [ -x "/bin/ls" ]; then
echo "/bin/ls 可执行"
fi
# 输出:/bin/ls 可执行
# 是否设置了SUID
if [ -u "/usr/bin/passwd" ]; then
echo "/usr/bin/passwd 设置了SUID"
fi
# 是否设置了SGID
if [ -g "/usr/bin/write" ]; then
echo "/usr/bin/write 设置了SGID"
fi
# 是否设置了粘滞位
if [ -k "/tmp" ]; then
echo "/tmp 设置了粘滞位"
fi
#!/bin/bash
file1="/etc/passwd"
file2="/etc/shadow"
# 文件是否为空
if [ -s "$file" ]; then
echo "$file 不为空"
fi
# 输出:/etc/passwd 不为空
# 文件是否被修改过(比另一个文件新)
if [ "$file1" -nt "$file2" ]; then
echo "$file1 比 $file2 新"
fi
# 文件是否比另一个文件旧
if [ "$file1" -ot "$file2" ]; then
echo "$file1 比 $file2 旧"
fi
# 两个文件是否为同一个文件(inode相同)
if [ "$file1" -ef "$file2" ]; then
echo "$file1 和 $file2 是同一个文件"
fi
文件测试运算符总结:
| 运算符 | 说明 |
|---|---|
| -e | 文件存在 |
| -f | 普通文件 |
| -d | 目录 |
| -r | 可读 |
| -w | 可写 |
| -x | 可执行 |
| -s | 文件不为空 |
| -L | 符号链接 |
| -b | 块设备 |
| -c | 字符设备 |
| -p | 管道文件 |
| -S | 套接字文件 |
| -nt | 文件比另一个新 |
| -ot | 文件比另一个旧 |
| -ef | 两个文件相同 |
#!/bin/bash
# 基本输出
echo "Hello World"
# 输出:Hello World
# 输出变量
name="东巴文"
echo "欢迎$name"
# 输出:欢迎东巴文
# 不换行输出
echo -n "不换行"
echo "输出"
# 输出:不换行输出
# 启用转义字符
echo -e "第一行\n第二行"
# 输出:
# 第一行
# 第二行
# 输出到标准错误
echo "错误信息" >&2
# 输出到文件
echo "写入文件" > file.txt
# 追加到文件
echo "追加内容" >> file.txt
# 显示命令执行结果
echo "当前目录:$(pwd)"
echo "当前用户:$USER"
echo选项:
| 选项 | 说明 |
|---|---|
| -n | 不换行 |
| -e | 启用转义字符 |
| -E | 禁用转义字符(默认) |
转义字符:
| 字符 | 说明 |
|---|---|
| \n | 换行 |
| \t | 制表符 |
| \ | 反斜杠 |
| \a | 警报 |
| \r | 回车 |
#!/bin/bash
# 基本格式化输出
printf "Hello, %s\n" "World"
# 输出:Hello, World
# 格式化字符串
name="东巴文"
age=25
printf "姓名:%s,年龄:%d\n" "$name" "$age"
# 输出:姓名:东巴文,年龄:25
# 格式化数字
printf "整数:%d\n" 123
printf "浮点数:%f\n" 3.14
printf "科学计数:%e\n" 1000000
# 宽度和对齐
printf "|%10s|\n" "hello" # 右对齐,宽度10
printf "|%-10s|\n" "hello" # 左对齐,宽度10
printf "|%10d|\n" 123 # 数字右对齐
printf "|%010d|\n" 123 # 用0填充
# 输出到文件
printf "写入文件\n" > file.txt
printf格式符:
| 格式符 | 说明 |
|---|---|
| %s | 字符串 |
| %d | 整数 |
| %f | 浮点数 |
| %c | 字符 |
| %x | 十六进制 |
| %o | 八进制 |
| %e | 科学计数法 |
东巴文理解:printf比echo更强大,支持格式化输出。
#!/bin/bash
# 基本输入
echo "请输入您的姓名:"
read name
echo "您好,$name!"
# 带提示的输入
read -p "请输入您的年龄:" age
echo "您的年龄是:$age"
# 输入密码(不显示)
read -s -p "请输入密码:" password
echo
echo "密码已接收"
# 限时输入(5秒)
read -t 5 -p "请在5秒内输入:" input
echo "您输入的是:$input"
# 限制输入字符数
read -n 1 -p "请输入一个字符:" char
echo
echo "您输入的是:$char"
# 读取多个变量
read -p "请输入姓名和年龄:" name age
echo "姓名:$name,年龄:$age"
# 读取到数组
read -a arr -p "请输入多个单词:"
echo "第一个单词:${arr[0]}"
echo "所有单词:${arr[@]}"
# 读取整行
read -p "请输入一行文字:" line
echo "您输入的是:$line"
read选项:
| 选项 | 说明 |
|---|---|
| -p | 提示信息 |
| -s | 静默模式(不显示输入) |
| -t | 超时时间(秒) |
| -n | 读取指定字符数 |
| -a | 读取到数组 |
| -d | 指定分隔符 |
| -r | 不处理转义字符 |
#!/bin/bash
# 用户信息收集脚本
echo "===== 用户信息收集 ====="
echo
# 输入姓名
read -p "请输入姓名:" name
# 输入年龄
while true; do
read -p "请输入年龄:" age
if [[ $age =~ ^[0-9]+$ ]]; then
break
else
echo "错误:年龄必须是数字"
fi
done
# 输入邮箱
while true; do
read -p "请输入邮箱:" email
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
break
else
echo "错误:邮箱格式不正确"
fi
done
# 输入密码
while true; do
read -s -p "请输入密码:" password1
echo
read -s -p "请再次输入密码:" password2
echo
if [ "$password1" = "$password2" ]; then
break
else
echo "错误:两次密码不一致"
fi
done
# 显示信息
echo
echo "===== 用户信息 ====="
echo "姓名:$name"
echo "年龄:$age"
echo "邮箱:$email"
echo "信息已保存"
文件描述符:
| 描述符 | 名称 | 说明 |
|---|---|---|
| 0 | stdin | 标准输入 |
| 1 | stdout | 标准输出 |
| 2 | stderr | 标准错误 |
输出重定向:
# 输出到文件(覆盖)
echo "Hello" > file.txt
# 输出到文件(追加)
echo "World" >> file.txt
# 标准输出重定向
ls > output.txt
# 标准错误重定向
ls /nonexistent 2> error.txt
# 标准输出和错误重定向到同一文件
ls /nonexistent > all.txt 2>&1
# 或使用&>(bash特有)
ls /nonexistent &> all.txt
# 丢弃输出
ls /nonexistent 2>/dev/null
# 标准输出和错误分别重定向
ls / > stdout.txt 2> stderr.txt
输入重定向:
# 从文件读取输入
wc -l < file.txt
# Here Document
cat << EOF
第一行
第二行
第三行
EOF
# Here String
grep "pattern" <<< "search in this string"
# 基本管道
ls | grep ".txt"
# 多个管道
cat file.txt | grep "error" | wc -l
# 管道与重定向结合
ls | grep ".txt" > txt_files.txt
# tee命令(同时输出到屏幕和文件)
ls | tee files.txt
# tee追加模式
ls | tee -a files.txt
东巴文理解:管道就像流水线,将一个命令的输出作为另一个命令的输入。
#!/bin/bash
name="东巴文"
age=25
# 使用echo输出变量值
echo "调试:name=$name, age=$age"
# 使用echo输出执行步骤
echo "开始执行第一步..."
# 第一步代码
echo "第一步完成"
echo "开始执行第二步..."
# 第二步代码
echo "第二步完成"
#!/bin/bash
# 显示执行的每条命令
set -x
name="东巴文"
echo "Hello, $name"
# 关闭调试
set +x
echo "调试已关闭"
# 输出示例:
# + name=东巴文
# + echo 'Hello, 东巴文'
# Hello, 东巴文
# + set +x
# 调试已关闭
set选项:
| 选项 | 说明 |
|---|---|
| -x | 显示执行的命令 |
| -v | 显示读取的行 |
| -e | 遇到错误立即退出 |
| -u | 使用未定义变量时报错 |
| -o pipefail | 管道中的错误也会导致脚本退出 |
# 执行时显示命令
bash -x script.sh
# 执行时显示读取的行
bash -v script.sh
# 执行时显示命令和读取的行
bash -xv script.sh
# 检查语法错误
bash -n script.sh
#!/bin/bash
# 执行命令
ls /nonexistent
# 检查退出状态
if [ $? -eq 0 ]; then
echo "命令执行成功"
else
echo "命令执行失败"
exit 1
fi
# 常见退出状态码
# 0 成功
# 1 一般错误
# 2 误用Shell命令
# 126 命令无法执行
# 127 命令未找到
# 128 退出参数无效
# 130 Ctrl+C中断
# 255 退出码超出范围
#!/bin/bash
# 错误处理函数
function error_exit() {
echo "[ERROR] $1" >&2
exit 1
}
# 使用错误处理
cd /nonexistent || error_exit "无法进入目录"
# 检查文件是否存在
[ -f "/etc/passwd" ] || error_exit "文件不存在"
# 检查命令是否成功
if ! command -v git &> /dev/null; then
error_exit "git命令未安装"
fi
echo "所有检查通过"
#!/bin/bash
# 清理函数
function cleanup() {
echo "执行清理操作..."
rm -f /tmp/tempfile
echo "清理完成"
}
# 捕获退出信号
trap cleanup EXIT
# 捕获中断信号(Ctrl+C)
trap 'echo "用户中断"; exit 1' INT
# 捕获错误
trap 'echo "发生错误"; exit 1' ERR
# 创建临时文件
touch /tmp/tempfile
# 脚本主要逻辑
echo "脚本执行中..."
# 脚本正常退出时,会自动执行cleanup函数
常见信号:
| 信号 | 说明 |
|---|---|
| EXIT | 脚本退出 |
| INT | 中断(Ctrl+C) |
| TERM | 终止 |
| HUP | 挂起 |
| ERR | 错误 |
东巴文最佳实践:使用trap确保脚本退出时执行清理操作。
#!/bin/bash
#===============================================================================
# 脚本名称:sysinfo.sh
# 脚本功能:显示系统信息
# 作者:东巴文
# 创建日期:2024-01-01
#===============================================================================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 显示标题
function show_header() {
echo -e "${BLUE}================================${NC}"
echo -e "${GREEN} 系统信息收集脚本${NC}"
echo -e "${BLUE}================================${NC}"
echo
}
# 显示系统信息
function show_system_info() {
echo -e "${YELLOW}[系统信息]${NC}"
echo "主机名:$(hostname)"
echo "系统:$(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
echo "内核:$(uname -r)"
echo "架构:$(uname -m)"
echo
}
# 显示CPU信息
function show_cpu_info() {
echo -e "${YELLOW}[CPU信息]${NC}"
echo "CPU型号:$(cat /proc/cpuinfo | grep "model name" | head -1 | cut -d':' -f2 | xargs)"
echo "CPU核心数:$(nproc)"
echo "CPU使用率:$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}')"
echo
}
# 显示内存信息
function show_memory_info() {
echo -e "${YELLOW}[内存信息]${NC}"
free -h | awk 'NR==1{printf "%-10s %10s %10s %10s\n", $1, $2, $3, $4}NR==2{printf "%-10s %10s %10s %10s\n", "内存", $2, $3, $4}NR==3{printf "%-10s %10s %10s %10s\n", "交换", $2, $3, $4}'
echo
}
# 显示磁盘信息
function show_disk_info() {
echo -e "${YELLOW}[磁盘信息]${NC}"
df -h | awk '$NF=="/"{printf "根分区:%s 总大小 %s 已使用 %s 可用 %s 使用率 %s\n", $NF, $2, $3, $4, $5}'
echo
}
# 显示网络信息
function show_network_info() {
echo -e "${YELLOW}[网络信息]${NC}"
echo "IP地址:$(hostname -I | awk '{print $1}')"
echo "MAC地址:$(ip link show | grep -E "ether" | awk '{print $2}' | head -1)"
echo
}
# 显示用户信息
function show_user_info() {
echo -e "${YELLOW}[用户信息]${NC}"
echo "当前用户:$USER"
echo "登录用户数:$(who | wc -l)"
echo
}
# 主函数
function main() {
show_header
show_system_info
show_cpu_info
show_memory_info
show_disk_info
show_network_info
show_user_info
echo -e "${GREEN}系统信息收集完成${NC}"
}
# 执行主函数
main
#!/bin/bash
#===============================================================================
# 脚本名称:backup.sh
# 脚本功能:文件备份脚本
# 作者:东巴文
# 创建日期:2024-01-01
#===============================================================================
# 设置变量
SOURCE_DIR="/home/user/data"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
LOG_FILE="/var/log/backup.log"
# 日志函数
function log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 检查源目录
if [ ! -d "$SOURCE_DIR" ]; then
log "错误:源目录 $SOURCE_DIR 不存在"
exit 1
fi
# 创建备份目录
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
log "创建备份目录:$BACKUP_DIR"
fi
# 开始备份
log "开始备份:$SOURCE_DIR"
log "备份文件:$BACKUP_DIR/$BACKUP_FILE"
# 执行备份
tar -czf "$BACKUP_DIR/$BACKUP_FILE" "$SOURCE_DIR" 2>&1 | tee -a "$LOG_FILE"
# 检查备份结果
if [ $? -eq 0 ]; then
log "备份成功"
log "备份文件大小:$(du -h "$BACKUP_DIR/$BACKUP_FILE" | cut -f1)"
else
log "备份失败"
exit 1
fi
# 删除旧备份(保留最近7个)
cd "$BACKUP_DIR"
ls -t backup_*.tar.gz | tail -n +8 | xargs rm -f
log "清理旧备份完成"
# 发送通知(可选)
# echo "备份完成:$BACKUP_FILE" | mail -s "备份通知" admin@example.com
log "备份任务完成"
#!/bin/bash
#===============================================================================
# 脚本名称:create_users.sh
# 脚本功能:批量创建用户
# 作者:东巴文
# 创建日期:2024-01-01
#===============================================================================
# 检查是否为root用户
if [ "$EUID" -ne 0 ]; then
echo "请使用root用户执行此脚本"
exit 1
fi
# 用户列表文件
USER_FILE="users.txt"
# 检查用户文件是否存在
if [ ! -f "$USER_FILE" ]; then
echo "错误:用户文件 $USER_FILE 不存在"
echo "请创建用户文件,格式:用户名 密码"
exit 1
fi
# 创建用户
while read username password; do
# 跳过空行和注释
if [ -z "$username" ] || [[ "$username" =~ ^# ]]; then
continue
fi
# 检查用户是否已存在
if id "$username" &>/dev/null; then
echo "用户 $username 已存在,跳过"
continue
fi
# 创建用户
useradd -m -s /bin/bash "$username"
# 设置密码
echo "$username:$password" | chpasswd
# 设置首次登录修改密码
chage -d 0 "$username"
echo "用户 $username 创建成功"
done < "$USER_FILE"
echo "所有用户创建完成"
✅ 理解Shell脚本基本概念 ✅ 掌握脚本编写规范 ✅ 学会变量与数据类型 ✅ 熟练使用运算符 ✅ 掌握输入输出操作 ✅ 学会脚本调试技巧
完成本章学习后,请确认您能够:
东巴文(db-w.cn) - 让Linux学习更简单