历史、补全与 Shell 配置
本节目标
使用 Bash history 和反向搜索回看已执行的命令,用 Tab 补全核对命令或路径;然后只在 DEVLAB_DIR 指定的练习目录中启动隔离 Bash 配置,并验证辅助函数与确定性摘要。
开始前状态
你已完成前四节:dev-lab 保留 starter 的说明文件,并已有 data/access.log 与 output/summary.txt。本节会先核对这个精确状态,再把它转换为 shell checkpoint;遇到意外文件或被改写的说明文件就停止,不覆盖。这里不会修改当前用户的启动配置、PATH、提示符或其他项目文件。先进入该 dev-lab 根目录,再保存绝对路径:
export DEVLAB_DIR="$PWD"
printf '%s\n' "$DEVLAB_DIR"
必要原理
history 显示当前 Shell 记住的命令;在 Bash 中按 Ctrl-R 可以反向搜索历史。历史里可能含有敏感参数、令牌、内部路径或误输入的密码,因此不要把历史输出直接贴到公开渠道,也不要把秘密放在命令行参数中。
按一次或两次 Tab 可以补全命令、目录和文件名,或在有多个候选项时显示选择范围。补全是 Shell 的基础能力,不依赖特定插件;它帮助减少拼写错误,但仍应先读懂补全出的目标路径。
交互 Bash 会读取指定 rc 文件;本节只需要知道 --rcfile 能把一次子 Shell 绑定到明确文件。隔离配置设置 DEVLAB_MODE,并提供 devlab-root 与 devlab-summary,不改写父 Shell。配置中的报告函数只调用 DEVLAB_DIR/scripts/report.sh,该脚本只重建 DEVLAB_DIR/output/summary.txt。
动手完成
运行位置: 完成 SHELL-04 后的 dev-lab 根目录。预期输出: 创建隔离配置、练习笔记和报告脚本,把已核对的 starter 说明替换为 shell checkpoint 说明,并输出固定三行摘要。失败处理: 若提示前一步状态不匹配,停止并检查具体路径;不要删除或覆盖提示中的意外内容。
prepare_shell_checkpoint() (
set -euo pipefail
require_exact() {
local relative_path="$1"
local expected_content="$2"
if [[ ! -f "$relative_path" || -L "$relative_path" ]] || ! cmp -s "$expected_content" "$relative_path"; then
printf '前一步状态不匹配,停止并检查:%s\n' "$relative_path"
exit 1
fi
}
for required_directory in config data notes output scripts; do
if [[ ! -d "$required_directory" || -L "$required_directory" ]]; then
printf '前一步目录必须是真实目录,停止并检查:%s\n' "$required_directory"
exit 1
fi
done
expected_paths='README.md
config
config/README.md
data
data/README.md
data/access.log
notes
notes/README.md
output
output/README.md
output/summary.txt
scripts
scripts/README.md'
actual_paths="$(find . -mindepth 1 -print | sed 's#^\./##' | LC_ALL=C sort)"
if [[ "$actual_paths" != "$expected_paths" ]]; then
printf '前一步目录含有缺失或意外路径,停止并检查。\n'
exit 1
fi
require_exact 'README.md' <(printf '%s\n' \
'# dev-lab' \
'' \
'这是 Sonnet Pages“开发环境”模块使用的安全练习目录。' \
'' \
'## 当前状态' \
'' \
'- 已进入统一的 Linux/Unix 命令行主线;' \
'- 已创建练习目录和说明文件;' \
'- 尚未初始化 Git 仓库;' \
'- 尚未创建脚本、配置或样例数据。' \
'' \
'后续教程只在这个练习目录中执行示例操作,不使用读者的真实项目。')
require_exact 'config/README.md' <(cat <<'EXPECTED'
# dev-lab 配置
这里将保存仅属于练习项目的配置示例。不要把真实账户、密钥或系统配置文件放入此目录。
EXPECTED
)
require_exact 'data/README.md' <(cat <<'EXPECTED'
# dev-lab 数据
这里将保存后续练习使用的可公开、可重建的样例数据,不放个人数据或不可恢复的原始资料。
EXPECTED
)
require_exact 'notes/README.md' <(cat <<'EXPECTED'
# dev-lab 笔记
这里记录命令观察、故障现象与练习结论,便于在后续教程中回看自己的学习证据。
EXPECTED
)
require_exact 'output/README.md' <(cat <<'EXPECTED'
# dev-lab 输出
这里将保存由练习明确生成、可以安全重新创建的输出文件;不要把唯一副本当作练习输出。
EXPECTED
)
require_exact 'scripts/README.md' <(cat <<'EXPECTED'
# dev-lab 脚本
这里将保存后续教程编写的小型练习脚本。执行前先阅读脚本内容,并只在 dev-lab 中运行。
EXPECTED
)
require_exact 'data/access.log' <(printf '%s\n' \
'2026-08-12T09:00:00Z GET / 200' \
'2026-08-12T09:00:01Z GET /docs/devenv/welcome 200' \
'2026-08-12T09:00:02Z GET /docs/devenv/module-route 200' \
'2026-08-12T09:00:03Z GET /missing 404' \
'2026-08-12T09:00:04Z POST /practice 200' \
'2026-08-12T09:00:05Z GET /old-link 404')
require_exact 'output/summary.txt' <(printf '%s\n' \
'total requests: 6' \
'successful requests: 4' \
'not found: 2')
cleanup_shell_drafts() {
rm -f .README.shell.next config/.bashrc.dev-lab.next notes/.shell-notes.md.next scripts/.report.sh.next
}
trap cleanup_shell_drafts EXIT
printf '%s\n' \
'# dev-lab Shell checkpoint' \
'' \
'这是终端与 Shell 板块完成后的权威参考状态。它保留固定访问日志、确定性摘要、隔离 Bash 配置和只会写入明确输出文件的报告脚本。' \
'' \
'请在此目录的父目录运行 `check-shell.sh`;checker 只读取目标,不会 source 配置或执行报告脚本。' \
> .README.shell.next
cat > config/.bashrc.dev-lab.next <<'EXPECTED'
export DEVLAB_MODE='practice'
devlab-root() {
printf '%s\n' "${DEVLAB_DIR:?set DEVLAB_DIR before using devlab-root}"
}
devlab-summary() {
bash "${DEVLAB_DIR:?set DEVLAB_DIR first}/scripts/report.sh" "$DEVLAB_DIR"
}
EXPECTED
cat > notes/.shell-notes.md.next <<'EXPECTED'
# Shell 练习笔记
历史记录可能包含参数、路径或令牌;先检查再分享或清理。Tab 补全帮助确认命令和文件名,但不代替阅读实际路径。
本 checkpoint 的 Bash 配置仅用于 `DEVLAB_DIR` 指定的隔离练习目录。
EXPECTED
cat > scripts/.report.sh.next <<'EXPECTED'
#!/usr/bin/env bash
set -euo pipefail
[[ "$#" -eq 1 ]] || { printf 'usage: report.sh DEV_LAB_DIR\n' >&2; exit 64; }
devlab_dir="${1:?usage: report.sh DEV_LAB_DIR}"
input="$devlab_dir/data/access.log"
output="$devlab_dir/output/summary.txt"
[[ -f "$input" ]] || { printf 'missing input: %s\n' "$input" >&2; exit 1; }
total="$(wc -l < "$input" | tr -d ' ')"
successful="$(grep -c ' 200$' "$input" || true)"
not_found="$(grep -c ' 404$' "$input" || true)"
{
printf 'total requests: %s\n' "$total"
printf 'successful requests: %s\n' "$successful"
printf 'not found: %s\n' "$not_found"
} > "$output"
EXPECTED
chmod 755 scripts/.report.sh.next
mv .README.shell.next README.md
mv config/.bashrc.dev-lab.next config/bashrc.dev-lab
mv notes/.shell-notes.md.next notes/shell-notes.md
mv scripts/.report.sh.next scripts/report.sh
rm config/README.md data/README.md notes/README.md output/README.md scripts/README.md
scripts/report.sh "$PWD"
)
if prepare_shell_checkpoint; then
unset -f prepare_shell_checkpoint
export DEVLAB_DIR="$PWD"
cat output/summary.txt
else
unset -f prepare_shell_checkpoint
false
fi
运行位置: shell checkpoint 根目录。预期输出: history 显示本终端已执行的命令;反向搜索能找到你输入过的片段。失败处理: 若找不到条目,先输入一条无敏感信息的 printf 'history practice\n',再重试 history 或 Ctrl-R;不要为了凑历史输入秘密。
history
printf 'history practice\n'
运行位置: 同一个目录。预期输出: Tab 补全帮助你得到 data/access.log 或 scripts/report.sh 的完整名称。失败处理: 有多个候选项时不要猜测;按 Tab 查看列表,再键入足够多的字符缩小范围。此步骤不写任何文件。
ls data/acc
ls scripts/rep
运行位置: 已设置 DEVLAB_DIR 的终端。预期输出: 新开一个只为练习服务的 Bash 子 Shell;它读取 checkpoint 内的 rc 文件。失败处理: 如果 DEVLAB_DIR 为空或路径错误,先 printf '%s\n' "$DEVLAB_DIR" 检查,绝不把 rc 文件改成用户目录中的文件。
bash --noprofile --rcfile "$DEVLAB_DIR/config/bashrc.dev-lab"
运行位置: 刚启动的练习子 Shell。预期输出: DEVLAB_MODE 为 practice,devlab-root 输出练习目录,devlab-summary 重建三行摘要。失败处理: 任一命令缺失时先 exit 回父 Shell,检查 DEVLAB_DIR 和 config/bashrc.dev-lab 的路径;不要 source 真实用户配置来补救。
printf '%s\n' "$DEVLAB_MODE"
devlab-root
devlab-summary
cat "$DEVLAB_DIR/output/summary.txt"
exit
运行位置: 已回到父 Shell。预期输出: 父 Shell 不存在本节隔离配置带来的 DEVLAB_MODE,且原有环境保持不变。失败处理: 若你此前已自行设置同名变量,记录原值即可;本节不要求修改或清理真实用户配置。
printf 'parent DEVLAB_MODE: %s\n' "${DEVLAB_MODE-unset}"
观察结果
历史与补全帮助你复用已知命令并减少输入,但它们不替你判断命令是否安全。bash --noprofile --rcfile 让交互子 Shell 跳过 profile,并用练习配置代替用户的默认 Bash rc:其中的 DEVLAB_MODE、devlab-root 和 devlab-summary 在退出后不改变父 Shell。devlab-summary 重建的摘要固定为总数 6、成功 4、未找到 2。
常见问题
Ctrl-R 没有找到命令怎么办? 先执行一条无敏感信息的练习命令,再按 Ctrl-R 搜索其中的词;不同终端对按键的显示可能不同。
需要修改 ~/.bashrc 或 ~/.zshrc 吗? 不需要,它们只作为以后自行阅读的可选主题,绝不是本节完成条件。安全警示:如未来确实学习真实配置,先备份目标文件、一次只做一个明确改动、用相应 Shell 做语法检查,并保留恢复备份的步骤;不要复制不明来源的配置片段。
完成检查
- 知道
history与Ctrl-R的用途,并能说明历史中敏感信息的风险; - 用 Tab 补全观察过命令或路径,不依赖特定插件;
- 设置
DEVLAB_DIR后以bash --noprofile --rcfile "$DEVLAB_DIR/config/bashrc.dev-lab"启动过隔离子 Shell; - 在子 Shell 验证
DEVLAB_MODE、devlab-root、devlab-summary和三行摘要,退出后确认父 Shell 未被本节改写; - 已运行 shell checker,得到
shell checkpoint: ok。
下一步
你现在可以在不污染用户环境的前提下使用命令、文本处理和隔离配置。下一板块会把同样的“先观察、限定目标、再验证”原则用于 Linux 基础。