如何模拟cron执行脚本的环境?

276

我通常会遇到几个问题,因为cron执行脚本时通常没有我的环境设置。是否有一种方法可以像cron一样调用bash(?),以便在安装脚本之前测试脚本?


我建议使用这种解决方案:http://unix.stackexchange.com/questions/27289/how-can-i-run-a-cron-command-with-existing-environmental-variables - rudi
将@gregseth的想法推广一下,我提供了这个解决方案:http://unix.stackexchange.com/questions/27289/how-can-i-run-a-cron-command-with-existing-environmental-variables/89596#89596 - Robert Brisita
一个 Cron 作业与在您通常的 shell 中运行的作业有许多不同之处(修改的环境只是其中之一)。我已经写了下面的答案,包括一个脚本,模仿我注意到的所有差异。 - Daladim
14个回答

431
将以下内容添加到您的crontab中(暂时):
* * * * * env > ~/cronenv

执行完后,请执行以下操作:

env - `cat ~/cronenv` /bin/sh

假设您的cron运行/bin/sh,这是默认设置,无论用户的默认shell是什么。

注:如果env包含更高级的配置,例如PS1=$(__git_ps1 " (%s)")$,它将会产生神秘的错误信息env: ": No such file or directory


6
请注意:如果将此内容添加到全局的 /etc/crontab 文件中,您需要同时指定用户名。例如:* * * * * root env > ~/cronenv。 - Greg
10
好的,简单明了的想法。对于急于使用的人,可以使用“* * * * *”来在下一分钟运行,完成后记得再次关闭,不要忘记啊;-) - Mads Buus
5
要返回到之前的 bash shell,请尝试输入:exit。 - spkane
3
我将它保存到/tmp/cronenv,以避免在该上下文中混淆~的含义。 - Max Williams
5
这个答案的重要性不容小觑,值得在书中撰写一个段落来加以说明。 - Xofo
显示剩余7条评论

63

Cron默认只提供以下环境变量:

  • HOME 用户主目录
  • LOGNAME 用户登录名
  • PATH=/usr/bin:/usr/sbin
  • SHELL=/usr/bin/sh

如果您需要更多环境变量,您可以在crontab中调度表之前导入一个脚本,其中定义了您的环境变量。


7
出于安全原因.通常不再是PATH的一部分。 - l0b0

59

两种方法:

  1. 导出 cron 环境并引用它:

    添加

    * * * * * env > ~/cronenv
    

    将其添加到您的crontab中,让它运行一次,然后关闭它,再运行

    env - `cat ~/cronenv` /bin/sh
    

    现在你正在一个具有cron环境的sh会话中。

  2. 将你的环境带到cron中

    你可以跳过上面的练习,只需在cron作业前面执行. ~/.profile即可。

  3. * * * * * . ~/.profile; your_command
    
  4. 使用 screen

    上述两种解决方案仍然失败,因为它们提供了一个连接到运行中的 X 会话的环境,并且具有访问 dbus 等的权限。例如,在 Ubuntu 上,nmcli (网络管理器)将在上述两种方法中工作,但在 cron 中仍然会失败。

  5. * * * * * /usr/bin/screen -dm
    

    将上述行添加到cron中,让其运行一次,然后关闭。连接到您的屏幕会话(screen -r)。如果您正在检查屏幕会话是否已创建(使用ps),请注意它们有时是大写的(例如ps | grep SCREEN

    现在即使是nmcli和类似的命令也会失败。


2
我偏爱Option1的语法是 env -i $(cat ~/cronenv) /bin/sh-i-(--ignore-environment)相同,但更易懂。$(...)\...``相同。 - wisbucky

22

你可以运行:

env - your_command arguments

这将使用空环境来运行您的命令。


4
cron 在完全空白的环境下无法运行,对吗? - jldupont
2
gregseth 确定了 cron 环境中包含的变量。您可以在命令行上包含这些变量。$ env - PATH="$PATH" command args - DragonFax
5
@DragonFax @dimba 我使用 env - HOME="$HOME" LOGNAME="$USER" PATH="/usr/bin:/bin" SHELL="$(which sh)" command arguments,这似乎可以解决问题。 - l0b0
这是在“敌对”或未知环境中测试脚本的一个简单有效的方法。如果你足够明确,它能在这里运行,那么它也能在cron下运行。 - Oli

16

14

六年后回答:环境不匹配问题是由 systemd 的“定时器”解决的问题之一,作为 cron 的替代品。无论您是通过 CLI 运行 systemd “服务”还是通过 cron,它都会收到完全相同的环境,避免了环境不匹配问题。

导致 cron 作业在手动传递时失败的最常见问题是由 cron 设置的默认限制性 $PATH,在 Ubuntu 16.04 上是这样的:

"/usr/bin:/bin"

相比之下,Ubuntu 16.04 上 systemd 设置的默认$PATH 是:

"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

因此,使用systemd计时器可以更轻松地找到二进制文件,从而有更好的机会。

但是,systemd计时器的缺点是设置它们需要更多的时间。首先,您需要创建一个“服务”文件来定义要运行的内容,然后创建一个“计时器”文件来定义运行的时间表,最后“启用”计时器以激活它。


10

创建一个 cron job,运行 env 命令并将标准输出重定向到文件中。 使用这个文件和 "env -" 命令来创建与 cron job 相同的环境。


抱歉,这让我困惑了。"env - script" 不就足够了吗? - Jorge Vargas
这将给你一个空的环境。当你通过cron运行脚本时,环境不是空的。 - Jens Carlberg

3
不要忘记,由于cron的父进程是init,它会在没有控制终端的情况下运行程序。你可以使用类似这样的工具来模拟:http://libslack.org/daemon/

3
接受的答案确实提供了一种使用cron环境运行脚本的方法。正如其他人指出的那样,这不是调试cron作业所需的唯一条件。
事实上,cron还使用非交互式终端,没有附加输入等。
如果有帮助的话,我编写了一个脚本,可以轻松地运行像cron一样运行命令/脚本。将其作为第一个参数调用您的命令/脚本即可。
此脚本也托管在Github上(可能会更新)。
#!/bin/bash
# Run as if it was called from cron, that is to say:
#  * with a modified environment
#  * with a specific shell, which may or may not be bash
#  * without an attached input terminal
#  * in a non-interactive shell

function usage(){
    echo "$0 - Run a script or a command as it would be in a cron job, then display its output"
    echo "Usage:"
    echo "   $0 [command | script]"
}

if [ "$1" == "-h" -o "$1" == "--help" ]; then
    usage
    exit 0
fi

if [ $(whoami) != "root" ]; then
    echo "Only root is supported at the moment"
    exit 1
fi

# This file should contain the cron environment.
cron_env="/root/cron-env"
if [ ! -f "$cron_env" ]; then
    echo "Unable to find $cron_env"
    echo "To generate it, run \"/usr/bin/env > /root/cron-env\" as a cron job"
    exit 0
fi

# It will be a nightmare to expand "$@" inside a shell -c argument.
# Let's rather generate a string where we manually expand-and-quote the arguments
env_string="/usr/bin/env -i "
for envi in $(cat "$cron_env"); do
   env_string="${env_string} $envi "
done

cmd_string=""
for arg in "$@"; do
    cmd_string="${cmd_string} \"${arg}\" "
done

# Which shell should we use?
the_shell=$(grep -E "^SHELL=" /root/cron-env | sed 's/SHELL=//')
echo "Running with $the_shell the following command: $cmd_string"


# Let's route the output in a file
# and do not provide any input (so that the command is executed without an attached terminal)
so=$(mktemp "/tmp/fakecron.out.XXXX")
se=$(mktemp "/tmp/fakecron.err.XXXX")
"$the_shell" -c "$env_string $cmd_string" >"$so" 2>"$se" < /dev/null

echo -e "Done. Here is \033[1mstdout\033[0m:"
cat "$so"
echo -e "Done. Here is \033[1mstderr\033[0m:"
cat "$se"
rm "$so" "$se"

2
默认情况下,cron 使用系统定义的sh来执行其作业。这可能是实际的 Bourne shell 或者 dashashksh或者 bash(或其他一些)通过符号链接关联到sh而运行在 POSIX 模式。

最好的做法是确保你的脚本拥有所需,并且不要认为系统会为它们提供任何东西。因此,你应该使用完整的目录规范并自己设置环境变量,如$PATH

这正是我要解决的问题。我们经常遇到脚本错误地假设某些东西的问题。使用完整路径、设置环境变量和所有垃圾代码最终会导致可怕的巨大且难以维护的 cron 行。 - Jorge Vargas
抱歉,我成长于Bash = Shell环境,所以很难记住其他(有时更好的)Shell环境。 - Jorge Vargas
1
@Jorge:crontab 中的行应该相当短。您应该在脚本(或包装脚本)中完成所有所需的设置。这是来自 crontab 的典型行的示例:0 0 * * 1 /path/to/executable >/dev/null 2>&1,然后,在“executable”中,我将为 $PATH 等设置值,并使用完整的目录规范来输入和输出文件等。例如:/path/to/do_something /another/path/input_file /another/path/to/output_file - Dennis Williamson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接