阿猫的博客

阿猫的博客

优雅地在 crontab 中运行脚本

2025-08-12

一般来说,像 Node.js、Python 这类解释型的语言,系统上都会存在比较多个版本,利用一些诸如 nvm、venv、poetry、uv 等环境管理器来管理。一般来说部署这些脚本都算是一个比较噩梦的体验(相比可以打包可执行文件的语言来说),因为还要涉及到配环境的问题,另外配定时任务更是另一番折磨。

本篇介绍一个技巧,避免在 crontab 中反复使用绝对路径,更优雅地进行部署。解决方案就是写一点 shell 脚本,比在 crontab 里面反复 && 要好。

首先你需要一个配置环境的脚本,以及运行定时任务的脚本。

env.sh

#!/bin/bash

# 如果你的 nvm 在其他目录,需要修改这里
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm

# 如果你需要指定node版本,可以反注释下面一行
#nvm use v21.4.5

script.sh

#!/bin/bash
$(which node) script.js

单独给定时任务写一个脚本的好处是,如果你的参数非常长,或者定时任务想要复杂一点的逻辑,例如脚本 1 和脚本 2,需要在脚本 1 运行成功后再运行脚本 2,如果失败就告警,可以写以下这样的脚本。

#!/bin/bash

# 定义需要执行的命令
COMMAND1="ls /path/to/nonexistent/dir"  # 示例:故意让command1失败(返回非0)
COMMAND2="echo 'command1执行成功,现在执行command2'"

# 执行command1并检查结果
$COMMAND1

# 判断返回值:0为成功,执行command2;否则处理失败
if [ $? -eq 0 ]; then
    echo "INFO: command1执行成功(退出码0)"
    $COMMAND2
else
    echo "ERROR: command1执行失败(退出码$?)"
    exit 1  # 可选:失败时退出脚本(返回非0码)
fi

crontab -e

*/1 * * * * (cd path; . env.sh; script.sh >> path/cron.log 2>&1; )

解释两个地方,第一个是 .source 命令的缩写,与直接运行一个 sh 不一样的是,source 会在当前 shell 中执行,而不是在一个子 shell 中执行。第二个是后面的重定向,>> 表示以增加的方式(而不是覆盖)把日志写入(重定向至)文件,2>&1 则表示把标准错误流(stderr)也重定向至标准输出流(stdout)。这些日志能够帮助你定位定时任务的问题。

如果调试通过,最好把这个日志变成可轮转的(超过指定大小/时间会清除),避免占用太多磁盘。也可以重定向到 /dev/null ,这样就会丢弃掉所有输出。

使用这种方式,比以下这种更优雅、灵活和好维护的多。

*/1 * * * * cd path && /home/user/.nvm/versions/node/v24.5.0/bin/node script.js

Reference

Run `node` scripts using `nvm` and `crontab` without hardcoding the node version · GitHub