写个 Bash 自动 git pull:三种姿势和 cron 里的坑

服务器上想自动拉最新代码,写个脚本挂 cron 跑。看着就两行,实际上有几个必吃的坑。

最基础写法

#!/bin/bash
cd /www/wwwroot/yourproject || exit 1
git pull
  • || exit 1cd 失败就退出,不然当前目录是家目录,git pull 会执行在错误的仓库
  • 保存为 pull.sh
  • chmod +x pull.sh
  • 执行 ./pull.sh

更稳的写法:不依赖 cd

git -C 可以直接指定仓库路径,省掉 cd 的失败风险:

#!/bin/bash
git -C /www/wwwroot/yourproject pull origin main

同一个脚本要处理多个仓库时特别顺手。

挂 cron 时会踩的坑

*/5 * * * * /www/wwwroot/scripts/pull.sh >> /var/log/pull.log 2>&1

看着没问题,实际经常挂。原因:

1. SSH 密钥没加载

用户手动跑时有 ssh-agent。cron 里没有,git@github.com 直接拒绝。解法:

  • 走 HTTPS + PAT,别用 SSH
  • 或在脚本里显式指定密钥:
export GIT_SSH_COMMAND="ssh -i /root/.ssh/deploy_key -o StrictHostKeyChecking=no"
git -C /www/wwwroot/yourproject pull origin main

2. PATH 太窄

cron 的 PATH 只有 /usr/bin:/bin,找不到自定义安装的 git。crontab 里手动补:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /www/wwwroot/scripts/pull.sh

3. 分支不明确

git pull 会走当前分支和默认 remote,可能拉到不该拉的分支。永远显式

git -C "$REPO" pull origin main

处理本地修改的完整版

生产脚本上还得防几件事:

#!/bin/bash
set -e

REPO=/www/wwwroot/yourproject
BRANCH=main

cd "$REPO"

# 1. 如果本地有修改,暂存(避免 pull 失败)
if [[ -n $(git status --porcelain) ]]; then
    echo "[$(date)] 本地有修改,先 stash"
    git stash push -m "auto-stash $(date +%s)"
fi

# 2. 拉最新
git fetch origin "$BRANCH"
git reset --hard "origin/$BRANCH"     # 直接对齐远程,别 merge

# 3. 清空 stash(不还原,防止和拉下来的冲突)
git stash clear

注意 git reset --hard 会覆盖本地任何修改。这是刻意的——线上机器上不该有手改,任何修改都视为”意外”直接抹掉。要保守就用 git pull --ff-only

大小写敏感的坑

服务器 Linux 大小写敏感,Windows / macOS 默认不敏感。仓库里同时存在:

RiderAuditController.php
RiderauditController.php

Linux 上拉下来会真的多出两个文件,Windows 上只能存一个,pull 立刻挂。

解法:清理仓库中的重名文件,用 git rm 保留一个:

git rm --cached RiderauditController.php
git commit -m "fix: 大小写重复"
git push

或者临时关掉大小写敏感(治标):

git config core.ignorecase true

一句话总结

git -C REPO pull origin BRANCH 是最省事的一行。挂 cron 要额外交代 PATH、SSH 密钥、分支名,生产脚本再套一层 reset --hard 防脏树。