需要执行一个包含多个 rsync 命令的脚本,但是每次执行 rsync 命令都要输入密码,使用 AI 写了一个,只需要执行脚本,首次输入密码,之后同一轮任务内都复用同一连接。

提示词

写一个 Debian12 下运行的脚本 `job.sh`,运行 `job.sh` 输入一次密码可以执行脚本内配置的多行 rsync 完整命令,即不用每次都需要输入密码。

要求: 

1. 执行脚本 `job.sh` 只需要输入一次密码,可以执行多次 rsync 命令
2. 脚本运行时间不确定,脚本中断、或者异常退出、或者 `Ctrl+C` 主动退出,就整个脚本也退出,包括但不限于连接
3. rsync 命令中输出的日志,实时在终端输出
4. 简单英文注释,禁止中文注释
5. 保持脚本简洁明了,不做过多比如命令不存在等检查——执行脚本,调用命令执行即可

伪代码如下

```bash
# 定义
REMOTE_USER="root"
REMOTE_HOST="192.168.1.115"
REMOTE_PORT="22"
# 配置
JOBS=[
  rsync -e "ssh -p${REMOTE_PORT}" -avpz --exclude 'softs/*' --delete /mnt/hgfs/e/a/ ${REMOTE_USER}@${REMOTE_HOST}:/opt/backup/a/
  rsync -e "ssh -p${REMOTE_PORT}" -avpz --exclude 'softs/*' --delete /mnt/hgfs/e/b/ ${REMOTE_USER}@${REMOTE_HOST}:/opt/backup/b/
]
# 执行逻辑
输出 任务开始
执行 JOBS[0]
执行 JOBS[1]
输出 任务完成
```

写完整的脚本,复制可使用

最终版本

使用 gpt 输入提示词得到的结果基本可以实现,根据需要小修改,已经满足需求。

JOBS 的配置直接写完整参数的,写法感觉累赘,继续优化写成一个函数,最终用的以下完整代码:

#!/usr/bin/env bash
set -euo pipefail

# Config
REMOTE_USER="root"
REMOTE_HOST="192.168.1.115"
REMOTE_PORT="22"

# SSH master connection
CONTROL_PATH="${HOME}/.ssh/cm-${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}"
SSH_OPTS=(
  -p "${REMOTE_PORT}"
  -o ControlMaster=auto
  -o ControlPersist=60m
  -o ControlPath="${CONTROL_PATH}"
  -o StrictHostKeyChecking=accept-new
  -o ServerAliveInterval=20
  -o ServerAliveCountMax=3
)

# Reuse the same SSH master connection
RSYNC_SSH=(ssh "${SSH_OPTS[@]}")

# Factor common rsync options
RSYNC_BASE="rsync -e \"${RSYNC_SSH[*]}\" -avpz --delete --timeout=600"

# helpers
# 把每个参数目录转成:
#   --include='dir/' --include='dir/***'
# 用 printf %q 做安全转义(空格、通配符都可)
make_inc() {
  local d
  for d in "$@"; do
    printf -- "--include=%q --include=%q " "$d/" "$d/***"
  done
}

# jobs
# 说明:
# 1) $(make_inc fastadmin) 会在数组定义时展开成
#    --include='fastadmin/' --include='fastadmin/***'
# 2) 你可以一次写多个目录:$(make_inc fastadmin uploads assets)
JOBS=(
  "${RSYNC_BASE} $(make_inc python) --exclude='*' \
    /mnt/hgfs/e/workspace/ \"${REMOTE_USER}@${REMOTE_HOST}:/opt/backup/workspace/\""

  "${RSYNC_BASE} $(make_inc fastadmin) --exclude='*' \
    /mnt/hgfs/e/www/ \"${REMOTE_USER}@${REMOTE_HOST}:/opt/backup/www/\""
)

cleanup() {
  echo "cleanup"
  ssh -O exit "${SSH_OPTS[@]}" "${REMOTE_USER}@${REMOTE_HOST}" >/dev/null 2>&1 || true
}
trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM
trap 'cleanup' EXIT

echo "SSH connection to ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}"
ssh -fN "${SSH_OPTS[@]}" "${REMOTE_USER}@${REMOTE_HOST}"

echo "++++++++++++++++ Tasks Start ++++++++++++++++"
for i in "${!JOBS[@]}"; do
  echo -e "\n///////////////////// $((i+1))"
  # rsync -e "ssh -p 22 -o ControlMaster=auto -o ControlPersist=60m -o ControlPath=/root/.ssh/[email protected]:22 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=20 -o ServerAliveCountMax=3" -avpz --delete --include='python/' --include='python/***' --exclude='*' /mnt/hgfs/e/workspace/ "[email protected]:/opt/backup/workspace/"
  # echo "${JOBS[$i]}"
  # sleep 3
  # Use bash -lc instead of eval to execute the full command line safely
  bash -lc "${JOBS[$i]}"
done
echo "++++++++++++++++ Tasks Done  ++++++++++++++++"