功能说明
publish 脚本支持干跑、详细输出、报告目录和帮助四个短选项,随后要求恰好两个位置参数:源目录与目标目录。解析完成后检查源目录存在、报告目录非空;干跑只打印计划,否则创建目录并用 find 逐文件复制,最后把汇总写入报告文件。帮助信息通过 heredoc 输出,未知选项与参数数量错误都返回状态码 2。
关键代码
optstring 以冒号开头,开启静默错误模式:缺少参数时 getopts 返回冒号并把选项名放入 OPTARG,未知选项返回问号,脚本据此输出自定义提示。循环结束后 shift “$((OPTIND - 1))” 移除已解析选项,剩余参数才是位置参数。find 使用 -print0 配合 read -d ‘’,避免文件名中的空格和换行破坏循环。相对路径通过 ${file#“$source_dir”/} 还原后再拼接目标路径。
使用注意
getopts 只支持短选项,长选项需要 getopt 或手工解析。源目录末尾带斜杠时,前缀截取可能留下多余斜杠,调用前应规范化。若目标目录位于源目录内部,逐文件遍历可能把正在复制的文件再次纳入,应该拒绝这种组合。示例未保留权限、时间戳和符号链接语义,cp 与 rsync 的行为不同,需按需求选择。报告 tee 失败会让整个脚本失败。
验证方式
准备一个包含若干文件的源目录,执行 bash publish.sh -n src dst 应只打印计划;去掉 -n 后目标目录出现相同文件,报告写入当前目录。执行 -o /tmp 可把报告放到 /tmp。传入不存在的选项返回 2,-o 后不跟参数返回 2,少给一个位置参数同样返回 2。用带空格的文件名验证复制不丢文件。
代码示例
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: publish.sh [-n] [-v] [-o DIR] <source-dir> <destination-dir>
-n dry run: print actions without copying
-v verbose: list every copied file
-o write a summary report into DIR (default: .)
-h show this help
EOF
}
dry_run=0
verbose=0
report_dir="."
while getopts ":nvo:h" opt; do
case "$opt" in
n) dry_run=1 ;;
v) verbose=1 ;;
o) report_dir="$OPTARG" ;;
h) usage; exit 0 ;;
:)
printf 'option -%s requires an argument\n' "$OPTARG" >&2
usage >&2
exit 2
;;
\?)
printf 'unknown option -%s\n' "$OPTARG" >&2
usage >&2
exit 2
;;
esac
done
shift "$((OPTIND - 1))"
if (( $# != 2 )); then
printf 'expected 2 positional arguments, got %d\n' "$#" >&2
usage >&2
exit 2
fi
source_dir=$1
destination_dir=$2
if [[ ! -d "$source_dir" ]]; then
printf 'source directory does not exist: %s\n' "$source_dir" >&2
exit 1
fi
if [[ -z "$report_dir" ]]; then
printf 'report directory must not be empty\n' >&2
exit 2
fi
if (( dry_run )); then
printf 'would copy %s -> %s\n' "$source_dir" "$destination_dir"
printf 'would write report to %s\n' "$report_dir"
exit 0
fi
mkdir -p -- "$destination_dir" "$report_dir"
copied=0
while IFS= read -r -d '' file; do
relative=${file#"$source_dir"/}
mkdir -p -- "$destination_dir/$(dirname -- "$relative")"
cp -- "$file" "$destination_dir/$relative"
copied=$((copied + 1))
if (( verbose )); then
printf 'copied %s\n' "$relative"
fi
done < <(find "$source_dir" -type f -print0)
printf 'copied %d files to %s\n' "$copied" "$destination_dir" |
tee -- "$report_dir/publish-report.txt"