性能与打包分发
# 第 13 章 性能与打包分发
# 目录介绍
# 13.1 性能优化
# 13.1.1 减少子 Shell——内建命令优先
每次启动子进程(fork)都有开销。能用 bash 内建命令做的,不调用外部程序:
#!/bin/bash
# ===== 子 Shell 开销对比 =====
# 启动一个外部命令 ≈ 额外 1-5ms(小但有积累效应)
# ❌ 字符串替换——调用 sed(外部进程)
result=$(echo "$text" | sed 's/old/new/')
# ✅ bash 内建参数替换
result="${text//old/new}"
# ❌ 转大写——调用 tr
upper=$(echo "$name" | tr '[:lower:]' '[:upper:]')
# ✅ bash 内建
upper="${name^^}"
# ❌ 取文件扩展名——调用 basename + sed
ext=$(echo "$file" | sed 's/.*\.//')
# ✅ bash 内建
ext="${file##*.}"
# ❌ 取目录名——调用 dirname
dir=$(dirname "$path")
# ✅ bash 内建
dir="${path%/*}"
# ❌ 算术运算——调用 expr
result=$(expr 10 + 3)
# ✅ bash 内建
result=$((10 + 3))
# ===== 内建命令 vs 外部命令速查 =====
# 外部命令 内建替代
# cat file $(<file) 或 while read
# echo $var | cmd cmd <<< "$var" 或 cmd < <(...)
# basename ${path##*/}
# dirname ${path%/*}
# sed 's/a/b/' ${var//a/b}
# tr 'A-Z' 'a-z' ${var,,} / ${var^^}
# expr 1 + 2 $((1 + 2))
# seq 1 10 {1..10}
# test / [ ] [[ ]]
# wc -l grep -c 或 awk 'END{print NR}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 13.1.2 减少管道——用 here-string 替代 echo | cmd
每个管道至少有 2 个进程(左边命令 + 右边命令)。很多场景可以省掉这个管道:
#!/bin/bash
# ❌ echo 管道给另一个命令——2 个进程
echo "$data" | grep "pattern"
echo "$data" | base64
echo "hello" | tr 'a-z' 'A-Z'
# ✅ here-string——省掉 echo 子进程
grep "pattern" <<< "$data"
base64 <<< "$data"
tr 'a-z' 'A-Z' <<< "hello"
# ❌ cat 管道给命令(经典的"无用的 cat")
cat file.txt | grep "pattern"
cat file.txt | while read line; do ... done
# ✅ 直接读取文件
grep "pattern" file.txt
while read line; do ... done < file.txt
# ===== 进程替换 <( ) 也能避免管道 =====
# ❌ 管道链
cat file.txt | sort | uniq -c | sort -rn
# ✅ 但这里管道是合理的——每个命令都有独立功能
# 真正可以优化的:
diff <(sort a.txt) <(sort b.txt) # 进程替换,省掉临时文件
# ===== 何时用管道、何时不用 =====
# ✅ 管道合理——每个命令承担不同角色
grep "ERROR" app.log | awk '{print $1}' | sort | uniq -c
# ❌ 管道可优化——只是把数据从 A 传到 B
echo "$var" | grep x → grep x <<< "$var"
cat file | while read → while read < file
echo "$x" | jq . → jq . <<< "$x"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 13.1.3 避免重复计算——缓存结果
#!/bin/bash
# ==== 反例:每次循环都开子进程重新计算 ====
for i in {1..100}; do
date_str=$(date +%Y%m%d) # ❌ 次次 fork date 进程
result=$(awk "BEGIN{print $i*3.14}") # ❌ 次次 fork awk 进程
echo "[$date_str] $result" >> log.txt # ❌ 次次打开+关闭文件
done
# 100 次循环 ≈ 300 个进程 fork!
# ==== 优化:缓存不变值 + 批量写入 =====
date_str=$(date +%Y%m%d) # ✅ 一次
output=""
for i in {1..100}; do
result=$(echo "$i * 3.14" | bc) # bc 仍要 fork,但至少不是 awk 了
# 更好的:用 bash 浮点模拟(见后文) 或提前算好数组
output+="[$date_str] $result"$'\n'
done
echo "$output" > log.txt # ✅ 一次写入
# ===== 缓存命令结果 =====
# ❌ 多次执行同一个昂贵命令
get_config() { grep "^$1=" /etc/myapp/config.ini | cut -d= -f2; }
for key in host port user pass; do
val=$(get_config "$key") # ❌ 每次都 grep 一次文件
done
# ✅ 一次读取,缓存到关联数组
declare -A CONFIG
while IFS='=' read -r key val; do
CONFIG["$key"]="$val"
done < /etc/myapp/config.ini
# 之后直接查数组,零 I/O
echo "${CONFIG[host]}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 13.1.4 数组 vs 文件——内存操作比磁盘快千倍
#!/bin/bash
# ===== 场景:处理 10 万行数据 =====
# ❌ 频繁文件 I/O——每行都要读写文件
while read -r ip; do
echo "$ip" >> /tmp/seen_ips.txt # 1 次写磁盘
count=$(grep -c "^$ip$" /tmp/seen_ips.txt) # 1 次读磁盘 + grep 进程
done < /tmp/ips.txt
# 10 万行:20 万次磁盘 I/O + 10 万个 grep 进程 → 极慢!
# ✅ 用关联数组——纯内存
declare -A ip_count
while read -r ip; do
((ip_count["$ip"]++))
done < /tmp/ips.txt
# 10 万行:0 次磁盘 I/O → 快 100+ 倍!
# ===== 数组批量操作 vs 循环 =====
# ❌ 循环中逐个写文件
for item in "${items[@]}"; do
echo "$item" >> output.txt
done
# ✅ 一次性输出
printf '%s\n' "${items[@]}" > output.txt
# ===== readarray 读文件到数组 =====
# ❌ 逐行循环
while IFS= read -r line; do
lines+=("$line")
done < file.txt
# ✅ 一次性读入
readarray -t lines < file.txt
# 或 mapfile -t lines < file.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 13.1.5 并行处理——wait / & / xargs -P
#!/bin/bash
# ===== 方式 1: & + wait —— 同时启动多个任务 =====
# 处理 100 张图片
for i in {1..100}; do
convert "img_${i}.jpg" -resize 800x600 "thumb_${i}.jpg" &
pids+=($!)
# 控制并发数:每 4 个等待一次
if (( ${#pids[@]} >= 4 )); then
wait -n # 等待任意一个完成
# 清理已完成的 PID(简化处理)
fi
done
wait # 等待剩余的完成
# ===== 方式 2: wait -n —— 任意完成即补新任务(bash 4.3+)=====
parallel_jobs() {
local max_jobs="${1:-4}"
shift
local -a cmds=("$@")
local active=0
for cmd in "${cmds[@]}"; do
# 若达到最大并发,等待一个完成
while (( active >= max_jobs )); do
wait -n
((active--))
done
eval "$cmd" &
((active++))
done
wait
}
# ===== 方式 3: xargs -P —— 最简洁的并行 =====
seq 1 100 | xargs -I {} -P 4 \
bash -c 'convert "img_{}.jpg" -resize 800x600 "thumb_{}.jpg"'
# -P 4 = 最多 4 个进程并行
# 读取文件列表并行处理
find /images -name "*.jpg" -print0 \
| xargs -0 -I {} -P 8 convert {} -resize 800x600 /thumbs/{}
# ===== 方式 4: GNU parallel(功能最强,需安装)=====
# parallel -j 4 convert {} -resize 800x600 /thumbs/{} ::: *.jpg
# ===== 并行处理性能对比 =====
# 单线程处理 100 个任务(每个 2 秒)→ 200 秒
# 4 并行 → ~50 秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 13.1.6 减少外部命令调用——用 bash 原生能力
#!/bin/bash
# ===== bash 内建浮点运算模拟 =====
# ❌ bc 外部命令
result=$(echo "scale=2; $a / $b" | bc)
# ✅ 整数模拟(按需放大小数点)
# 13.5 × 2.3 → 135 × 23 / 100 = 3105 / 100 = 31.05
mul_float() {
# 简单版(假设输入格式一致)
local a="${1//./}" b="${2//./}"
local dec_a=$((${#1} - ${1%%.*} - 1))
local dec_b=$((${#2} - ${2%%.*} - 1))
dec_a=${dec_a#-}; dec_b=${dec_b#-}
local result=$(( (a * b) / (10 ** (dec_a + dec_b)) ))
echo "$result"
}
# ===== bash 内建 random(不用外部命令)=====
# ❌ 每次调外部命令
random=$(head -c 4 /dev/urandom | od -An -tu4)
# ✅ 使用 $RANDOM
num=$RANDOM # 0~32767
# ===== bash 内建求长度 =====
# ❌ wc -c
len=$(echo -n "$str" | wc -c)
# ✅ ${#var}
len=${#str}
# ===== bash 字符串切割 =====
# ❌ cut
first=$(echo "$line" | cut -d':' -f1)
# ✅ bash IFS
IFS=':' read -r first _ <<< "$line"
# ===== bash 时间戳 =====
# ❌ date +%s
start=$(date +%s)
# ✅ printf (bash 4.2+)
printf -v start '%(%s)T' -1 # 当前时间戳
# 比 date 快约 100 倍(不 fork)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 13.1.7 实战:一行脚本的 100 倍加速
#!/bin/bash
# ===== 场景:统计 Nginx 日志中每个 IP 的请求数 =====
# 文件大小:500MB,1000 万行
# ❌ 原始写法(耗时 ~45 秒)
time {
for ip in $(awk '{print $1}' access.log | sort -u); do
count=$(grep -c "^$ip " access.log)
echo "$ip $count"
done | sort -k2 -rn | head -10
}
# 问题:sort -u 遍历一次,每个 IP 再 grep -c 一次 = O(n²)
# ✅ 优化 1:awk 一次搞定(耗时 ~8 秒,5.6x 提升)
time awk '{ip[$1]++} END{for(i in ip) print ip[i], i}' access.log | sort -rn | head -10
# ✅ 优化 2:sort + uniq 管道(耗时 ~5 秒,9x 提升)
time awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# ✅ 优化 3:LC_ALL 优化 sort(耗时 ~2 秒,22x 提升)
time LC_ALL=C awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# LC_ALL=C 告诉 sort 不要考虑 locale 排序(按字节序,快很多)
# ===== 性能对比总结 =====
# 方案 耗时 相对
# 原始嵌套循环 45s 1x
# awk 关联数组 8s 5.6x
# sort+uniq管道 5s 9x
# +LC_ALL优化 2s 22x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 13.2 打包分发
# 13.2.1 脚本打包——tar 归档 + 目录结构
#!/bin/bash
# ===== 标准脚本项目目录结构 =====
# mytool/
# ├── bin/
# │ └── mytool.sh ← 主入口
# ├── lib/
# │ ├── utils.sh
# │ └── config.sh
# ├── conf/
# │ └── mytool.conf
# ├── doc/
# │ └── README.md
# ├── install.sh ← 安装脚本
# ├── uninstall.sh ← 卸载脚本
# └── VERSION
# ===== 打包命令 =====
# 创建发布包
tar -czf mytool-v1.0.0.tar.gz \
--exclude=".git" \
--exclude="*.swp" \
--exclude="*~" \
--exclude="__pycache__" \
mytool/
# 生成校验和
shasum -a 256 mytool-v1.0.0.tar.gz > mytool-v1.0.0.tar.gz.sha256
# ===== 打包脚本 =====
cat > /opt/mytool/scripts/package.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
PROJECT="mytool"
VERSION=$(cat VERSION 2>/dev/null || echo "dev-$(date +%Y%m%d)")
ARCHIVE="${PROJECT}-v${VERSION}.tar.gz"
echo "===== 打包 $ARCHIVE ====="
# 创建临时目录
TMPDIR=$(mktemp -d)
cp -r . "$TMPDIR/$PROJECT"
# 清理开发痕迹
find "$TMPDIR/$PROJECT" -name ".DS_Store" -delete
find "$TMPDIR/$PROJECT" -name "*.swp" -delete
# 打包
tar -czf "$ARCHIVE" -C "$TMPDIR" "$PROJECT"
# 校验和
shasum -a 256 "$ARCHIVE" > "${ARCHIVE}.sha256"
# 清理
rm -rf "$TMPDIR"
echo "✅ 打包完成: $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"
echo " 校验和: ${ARCHIVE}.sha256"
SCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 13.2.2 自解压脚本——一个文件搞定一切
#!/bin/bash
# ===== 原理:Shell 脚本 + tar 数据拼接 =====
# 文件结构:
# |---- install.sh 部分 ----|---- tar.gz 数据部分 ----|
# 脚本读到自己的第 N 行之后,当作 tar 数据解压
# ===== 创建自解压脚本 =====
cat > /opt/mytool/scripts/make_self_extracting.sh << 'SCRIPT'
#!/bin/bash
# 创建自解压安装包
PAYLOAD="mytool-v1.0.0.tar.gz"
OUTPUT="mytool-installer.sh"
# 安装脚本头部
cat > "$OUTPUT" << 'HEADER'
#!/bin/bash
set -e
echo "===== MyTool 安装程序 ====="
INSTALL_DIR="${1:-/usr/local/mytool}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
# 提取嵌入的 tar.gz(从脚本自身第 __PAYLOAD_BELOW__ 行之后)
ARCHIVE_LINE=$(awk '/^__PAYLOAD_BELOW__$/ {print NR+1; exit}' "$0")
tail -n +$ARCHIVE_LINE "$0" | tar -xz -C "$TMPDIR"
echo "安装到 $INSTALL_DIR ..."
mkdir -p "$INSTALL_DIR"
cp -r "$TMPDIR"/*/* "$INSTALL_DIR/"
# 设置权限
chmod +x "$INSTALL_DIR/bin/"*.sh
# 创建软链接
ln -sf "$INSTALL_DIR/bin/mytool.sh" /usr/local/bin/mytool
echo "✅ 安装完成!运行 'mytool --help' 查看用法"
exit 0
__PAYLOAD_BELOW__
HEADER
# 追加 tar.gz 数据
cat "$PAYLOAD" >> "$OUTPUT"
chmod +x "$OUTPUT"
echo "✅ 自解压脚本已生成: $OUTPUT"
SCRIPT
# ===== 使用方式 =====
# chmod +x mytool-installer.sh
# sudo ./mytool-installer.sh /opt/mytool
# 一行命令完成下载+安装+配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 13.2.3 依赖检查——安装即用
#!/bin/bash
# ===== 依赖声明与检查 =====
declare -A REQUIRED_CMDS=(
["jq"]="brew install jq / apt install jq"
["curl"]="brew install curl / apt install curl"
["docker"]="https://docs.docker.com/get-docker/"
)
declare -A REQUIRED_BASH=(
["version"]="4.0"
)
check_dependencies() {
local missing=0
# 检查 bash 版本
if (( BASH_VERSINFO[0] < 4 )); then
echo "❌ 需要 bash >= 4.0,当前: $BASH_VERSION"
((missing++))
fi
# 检查命令
for cmd in "${!REQUIRED_CMDS[@]}"; do
if ! command -v "$cmd" &>/dev/null; then
echo "❌ 缺少命令: $cmd"
echo " 安装方式: ${REQUIRED_CMDS[$cmd]}"
((missing++))
else
echo "✅ $cmd ($(command -v "$cmd"))"
fi
done
if (( missing > 0 )); then
echo ""
echo "检测到 $missing 个缺失依赖,请安装后重试"
exit 1
fi
echo "✅ 所有依赖已满足"
}
# check_dependencies
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 13.2.4 安装脚本规范——install.sh 标准模板
#!/bin/bash
# ============================================
# MyTool 安装脚本
# 用法: curl -fsSL https://example.com/install.sh | bash
# wget -qO- https://example.com/install.sh | bash
# 或下载后手动: bash install.sh
# ============================================
set -euo pipefail
# ---- 配置 ----
APP_NAME="mytool"
DEFAULT_INSTALL_DIR="/usr/local/${APP_NAME}"
BIN_DIR="/usr/local/bin"
VERSION="1.0.0"
# ---- 工具函数 ----
info() { echo "[INFO] $*"; }
warn() { echo "[WARN] $*" >&2; }
error() { echo "[ERROR] $*" >&2; exit 1; }
# ---- 依赖检查 ----
check_deps() {
info "检查依赖..."
for cmd in curl tar; do
command -v "$cmd" &>/dev/null || error "缺少命令: $cmd"
done
}
# ---- 主体安装 ----
do_install() {
local install_dir="${1:-$DEFAULT_INSTALL_DIR}"
info "安装 $APP_NAME v${VERSION} 到 $install_dir ..."
# 创建目录
mkdir -p "$install_dir/bin" "$install_dir/conf" "$install_dir/logs"
# 复制文件(自解压模式用 tar,单文件模式直接复制)
if [[ -f "${APP_NAME}.tar.gz" ]]; then
tar -xzf "${APP_NAME}.tar.gz" -C "$install_dir" --strip-components=1
else
# 假设当前脚本与源码在同一目录
cp -r ./* "$install_dir/"
fi
# 设置权限
chmod +x "$install_dir/bin/"*.sh
chmod 600 "$install_dir/conf/"*.conf 2>/dev/null || true
# 创建软链接
ln -sf "$install_dir/bin/${APP_NAME}.sh" "${BIN_DIR}/${APP_NAME}"
info "✅ 安装完成"
info "运行 '${APP_NAME} --help' 查看用法"
}
# ---- 卸载 ----
do_uninstall() {
local install_dir="${1:-$DEFAULT_INSTALL_DIR}"
info "卸载 $APP_NAME ..."
rm -rf "$install_dir"
rm -f "${BIN_DIR}/${APP_NAME}"
info "✅ 卸载完成"
}
# ---- 入口 ----
case "${1:-install}" in
install)
check_deps
do_install "${2:-$DEFAULT_INSTALL_DIR}"
;;
uninstall)
do_uninstall "${2:-$DEFAULT_INSTALL_DIR}"
;;
-h|--help)
echo "用法: $0 [install|uninstall] [安装路径]"
;;
*)
error "未知命令: $1"
;;
esac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 13.2.5 版本管理与更新机制
#!/bin/bash
# ===== VERSION 文件 =====
echo "1.0.0" > VERSION
# ===== 版本比较函数 =====
version_compare() {
# 返回:0=相等 1=第一个大 2=第二个大
local v1="${1#v}" v2="${2#v}"
[[ "$v1" == "$v2" ]] && return 0
local IFS=.
local i ver1=($v1) ver2=($v2)
for ((i=0; i<${#ver1[@]} || i<${#ver2[@]}; i++)); do
local n1=${ver1[$i]:-0}
local n2=${ver2[$i]:-0}
if (( n1 > n2 )); then return 1; fi
if (( n1 < n2 )); then return 2; fi
done
return 0
}
# ===== 自更新脚本 =====
cat > /opt/mytool/scripts/update.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
APP="mytool"
REPO_URL="https://github.com/yourname/mytool"
UPDATE_URL="${REPO_URL}/releases/latest/download/${APP}.tar.gz"
VERSION_FILE="/usr/local/${APP}/VERSION"
check_update() {
local current=$(cat "$VERSION_FILE" 2>/dev/null || echo "0.0.0")
local latest=$(curl -sf "${REPO_URL}/releases/latest" | grep -oP 'v\d+\.\d+\.\d+' | head -1)
if [[ -z "$latest" ]]; then
echo "无法获取最新版本信息"
return 1
fi
echo "当前版本: $current"
echo "最新版本: $latest"
version_compare "$current" "$latest"
case $? in
0) echo "已是最新版本" ;;
1) echo "当前版本更高(本地开发版?)" ;;
2) echo "有新版本可用!运行 $0 update 更新" ;;
esac
}
do_update() {
local tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
echo "下载新版本..."
curl -fsSL "$UPDATE_URL" -o "$tmpdir/update.tar.gz"
echo "解压..."
tar -xzf "$tmpdir/update.tar.gz" -C "$tmpdir"
echo "安装..."
bash "$tmpdir/$APP/install.sh" install
echo "✅ 更新完成: $(cat "$VERSION_FILE")"
}
case "${1:-check}" in
check) check_update ;;
update) do_update ;;
*) echo "用法: $0 {check|update}" ;;
esac
SCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 13.2.6 一键安装卸载 + 帮助文档
#!/bin/bash
# ===== 帮助文档模板 =====
cat > /opt/mytool/bin/mytool.sh << 'TOOLSCRIPT'
#!/bin/bash
set -euo pipefail
APP_VERSION="1.0.0"
usage() {
cat << 'HELP'
我的工具 - MyTool v1.0.0
用法:
mytool <命令> [选项]
命令:
install 安装 MyTool
uninstall 卸载 MyTool
check 检查运行环境
update 更新到最新版本
status 查看当前状态
全局选项:
-h, --help 显示帮助
-v, --version 显示版本
--debug 调试模式
示例:
mytool install /opt/mytool 安装到指定目录
mytool check 检查环境
mytool update 在线更新
mytool status 查看版本和配置
更多信息: https://github.com/yourname/mytool
HELP
}
check() {
echo "===== 环境检查 ====="
echo "OS: $(uname -s) $(uname -m)"
echo "Bash: $BASH_VERSION"
echo "安装路径: ${INSTALL_DIR:-未安装}"
echo "版本: ${APP_VERSION:-未知}"
echo ""
local missing=0
for cmd in jq curl; do
if command -v "$cmd" &>/dev/null; then
echo "✅ $cmd: $(command -v "$cmd")"
else
echo "❌ $cmd: 未安装"
((missing++))
fi
done
[[ "$missing" -eq 0 ]] && echo "✅ 所有依赖正常" || echo "⚠️ 缺少 $missing 个依赖"
}
status() {
if [[ -f "/usr/local/mytool/VERSION" ]]; then
echo "MyTool 已安装"
echo "版本: $(cat /usr/local/mytool/VERSION)"
echo "路径: /usr/local/mytool"
ls /usr/local/mytool/bin/
else
echo "MyTool 未安装"
echo "运行 'mytool install' 安装"
fi
}
# ---- 入口 ----
case "${1:-}" in
install) shift; exec /opt/mytool/install.sh install "$@" ;;
uninstall) shift; exec /opt/mytool/install.sh uninstall "$@" ;;
check) check ;;
update) exec /opt/mytool/scripts/update.sh update ;;
status) status ;;
-h|--help) usage ;;
-v|--version) echo "v$APP_VERSION" ;;
"") usage ;;
*) echo "未知命令: $1"; usage; exit 1 ;;
esac
TOOLSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 13.3 思考题
性能基准测试:写两个版本的文本处理脚本(一个用外部命令链,一个用 bash 内建),用
time对比 10 万行文件的处理速度,输出差异倍数。智能并发:写一个并行下载脚本,接受一个 URL 列表文件作为参数,限制最大并发数,下载完成后输出每个文件的下载状态(成功/失败/速度)。
自解压安装包:制作一个最小化的"一键安装"脚本,其中嵌入
.tar.gz数据,执行时自动解压、检查依赖、复制文件、创建软链接、输出成功提示。版本升级检测:在已部署的脚本中加入"启动时检查更新"功能——运行时异步查询最新版本,如果有新版本则输出提示(不阻塞主逻辑)。
性能剖析:用
set -x+ 时间统计,写一个脚本性能剖析工具,输出每个函数的执行时间排行,找出性能瓶颈。
上次更新: 2026/06/17, 12:47:39