rsync 从 shell 成功,但从 crontab 抱怨 "syntax or usage error"

rsync succeeds from the shell but complains of "syntax or usage error" from crontab

以下 crontab 行(在 CentOS 7.6 上)

@daily rsync -rav --remove-source-files --files-from=<(find /home/qa/buildbot/master/packages/rhel7 -type f -mtime +30 -exec basename {} \;) /home/qa/buildbot/master/packages/rhel7 192.168.1.31:/local/raid0/buildbotpackages/packages/rhel7

失败,并向我发送以下电子邮件:

From: (Cron Daemon) <crontab@docker.local>
Subject: Cron <qa@docker> rsync -rav --remove-source-files --files-from=<(find /home/qa/buildbot/master/packages/rhel7 -type f -mtime +30 -exec basename {} \;) /home/qa/buildbot/master/packages/rhel7 192.168.1.31:/local/raid0/buildbotpackages/packages/rhel7


rsync: failed to open files-from file <(find /home/qa/buildbot/master/packages/rhel7 -type f -mtime +30 -exec basename {} ;): No such file or directory
rsync error: syntax or usage error (code 1) at main.c(1567) [client=3.1.2]

我查看了rsync的main.c的源代码,但是看不到1567行的相关性:

   1557     SIGACTMASK(SIGINT, sig_int);
   1558     SIGACTMASK(SIGHUP, sig_int);
   1559     SIGACTMASK(SIGTERM, sig_int);
   1560 #if defined HAVE_SIGACTION && HAVE_SIGPROCMASK
   1561     sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
   1562 #endif
   1563 
   1564     /* Ignore SIGPIPE; we consistently check error codes and will
   1565      * see the EPIPE. */
   1566     SIGACTION(SIGPIPE, SIG_IGN);
   1567 #ifdef SIGXFSZ
   1568     SIGACTION(SIGXFSZ, SIG_IGN);
   1569 #endif
   1570 
   1571     /* Initialize change_dir() here because on some old systems getcwd
   1572      * (implemented by forking "pwd" and reading its output) doesn't
   1573      * work when there are other child processes.  Also, on all systems
   1574      * that implement getcwd that way "pwd" can't be found after chroot. */
   1575     change_dir(NULL, CD_NORMAL);
   1576 
   1577     init_flist();

此外,当我 运行 来自 shell 的确切 crontab 行时,它起作用了:

qa@docker /tmp$ rsync -rav --remove-source-files --files-from=<(find /home/qa/buildbot/master/packages/rhel7 -type f -mtime +30 -exec basename {} \;) /home/qa/buildbot/master/packages/rhel7 192.168.1.31:/local/raid0/buildbotpackages/packages/rhel7
sending incremental file list

sent 18 bytes  received 12 bytes  20.00 bytes/sec
total size is 0  speedup is 0.00
qa@docker /tmp$

关于如何调试这个问题有什么想法吗?

中断的 cron 作业通常是由于在不同于一个 cron 使用的 shell 中测试命令造成的。大多数流行的交互式 shells,如 bashzsh 以及标准 /bin/sh(由 cron 使用)具有相似的基本语法,因为它们都是 Bourne 的后裔shell。它们之间的相似性非常强,您可以暂时认为 cron 的命令语法与您的登录相同 shell.

当您将更复杂的命令放入 crontab 时,您会发现它们之间存在差异。在您的示例中,我怀疑命令替换运算符 <(...)。这种类型的替换在 Bourne shell 中不存在,我认为 POSIX 也没有采用它,所以我不会在 cron 作业中信任它。

在您的系统上 /bin/sh 中测试您的命令,只需 运行 在您的另一个中 shell:

BIG-FANCY-PROMPT%>$ sh
$ rsync -rav --remove-source-files --files-from=<(find /home/qa/buildbot/master/packages/rhel7 -type f -mtime +30 -exec basename {} \;) /home/qa/buildbot/master/packages/rhel7 192.168.1.31:/local/raid0/buildbotpackages/packages/rhel7
... probably some error message here...
$ exit
BIG-FANCY-PROMPT%>$ _

如果你想不出用 sh 语法重写命令的方法,你总是可以显式调用 bash 或你喜欢的任何其他 shell:

@daily bash -c '...'

这种方式涉及额外的引用层,因此 sh 会将整个命令传递给 bash 而无需尝试解析它。如果这变得太困难,那么将你的命令放在一个脚本中,其中包含一个漂亮的 #! 行和 运行 that 来自 cron.