编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • ScriptHub 脚本工具箱
  • Python

  • Shell-Bash

    • Shell & Bash 从0到1实战专栏
    • Shell 入门与变量
    • 流程控制与函数
    • 数据与 IO 处理
    • grep 搜索实战
    • sed 与 awk 编程
    • 文件查找与统计
    • 日志监控与告警
    • 备份进程与磁盘
    • 用户与服务管理
    • 网络调度与部署
    • 调试与脚本规范
    • 安全与兼容处理
    • 性能与打包分发
      • 13.1 性能优化
        • 13.1.1 减少子 Shell——内建命令优先
        • 13.1.2 减少管道——用 here-string 替代 echo | cmd
        • 13.1.3 避免重复计算——缓存结果
        • 13.1.4 数组 vs 文件——内存操作比磁盘快千倍
        • 13.1.5 并行处理——wait / & / xargs -P
        • 13.1.6 减少外部命令调用——用 bash 原生能力
        • 13.1.7 实战:一行脚本的 100 倍加速
      • 13.2 打包分发
        • 13.2.1 脚本打包——tar 归档 + 目录结构
        • 13.2.2 自解压脚本——一个文件搞定一切
        • 13.2.3 依赖检查——安装即用
        • 13.2.4 安装脚本规范——install.sh 标准模板
        • 13.2.5 版本管理与更新机制
        • 13.2.6 一键安装卸载 + 帮助文档
      • 13.3 思考题
  • 工具脚本

  • ScriptHub
  • Shell-Bash
杨充
2022-08-29
目录

性能与打包分发

# 第 13 章 性能与打包分发

# 目录介绍

  • 13.1 性能优化
    • 13.1.1 减少子 Shell——内建命令优先
    • 13.1.2 减少管道——用 here-string 替代 echo | cmd
    • 13.1.3 避免重复计算——缓存结果
    • 13.1.4 数组 vs 文件——内存操作比磁盘快千倍
    • 13.1.5 并行处理——wait / & / xargs -P
    • 13.1.6 减少外部命令调用——用 bash 原生能力
    • 13.1.7 实战:一行脚本的 100 倍加速
  • 13.2 打包分发
    • 13.2.1 脚本打包——tar 归档 + 目录结构
    • 13.2.2 自解压脚本——一个文件搞定一切
    • 13.2.3 依赖检查——安装即用
    • 13.2.4 安装脚本规范——install.sh 标准模板
    • 13.2.5 版本管理与更新机制
    • 13.2.6 一键安装卸载 + 帮助文档
  • 13.3 思考题

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 13.3 思考题

  1. 性能基准测试:写两个版本的文本处理脚本(一个用外部命令链,一个用 bash 内建),用 time 对比 10 万行文件的处理速度,输出差异倍数。

  2. 智能并发:写一个并行下载脚本,接受一个 URL 列表文件作为参数,限制最大并发数,下载完成后输出每个文件的下载状态(成功/失败/速度)。

  3. 自解压安装包:制作一个最小化的"一键安装"脚本,其中嵌入 .tar.gz 数据,执行时自动解压、检查依赖、复制文件、创建软链接、输出成功提示。

  4. 版本升级检测:在已部署的脚本中加入"启动时检查更新"功能——运行时异步查询最新版本,如果有新版本则输出提示(不阻塞主逻辑)。

  5. 性能剖析:用 set -x + 时间统计,写一个脚本性能剖析工具,输出每个函数的执行时间排行,找出性能瓶颈。

#Shell#技巧
上次更新: 2026/06/17, 12:47:39
安全与兼容处理
工具脚本速查

← 安全与兼容处理 工具脚本速查→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式