14 个版本

0.3.4 2023年10月27日
0.3.3 2023年8月22日
0.3.2 2023年3月8日
0.3.0 2022年11月7日
0.1.6 2020年9月9日

#293解析实现

每月 36 次下载

Apache-2.0

37KB
524

evry

以 shell 脚本为中心的任务调度器;使用退出码来确定控制流。我通常在 bgproc 后面调用这个程序。

安装

安装 rust/cargo,然后

cargo install evry

原理

A tool to manually run commands -- periodically.
Uses shell exit codes to determine control flow in shell scripts

Usage:
  evry <describe duration>... <-tagname>
  evry location <-tagname>
  evry duration <some duration string...>
  evry help

最好用示例来说明

evry2-scrapesite&&wget"https://" -o....

换句话说,每两周运行一次 wget 命令。

如果命令在过去两周内已运行,evry 会以不成功的退出码退出(以下为更多持续时间示例),这意味着不会运行 wget 命令。

evry 以成功的退出码退出时,它会将该标签的当前时间保存到元数据文件中(-scrapesite)。这样,当再次使用该标签运行 evry 时,它可以比较当前时间与该文件。

这可以被认为是 cron 的替代品,但操作不会在后台运行。它要求您自己调用命令,但如果在您描述的时间范围内已经运行,则不会运行。(然而,将其包装在后台无限循环中并不困难,这正是 bgproc 所做的)

您可以在后台运行一个无限循环,如下所示

while true; do
  evry 1 month -runcommand && run command
  sleep 60
done

... 尽管它试图每 60 秒运行一次命令,但 evry 以不成功的退出码退出,因此 run command 每月只会运行一次。

-runcommand只是一个任意的标签名,这样evry就可以保存关于要运行的任务/作业的元数据。它可以任意选择,它的唯一用途是唯一标识某些任务,并将元数据文件保存到您的本地数据目录。如果您想覆盖默认位置,可以设置EVRY_DIR变量。例如,在您的shell配置文件中

export EVRY_DIR="$HOME/.local/share/tags"

由于它不在更大的上下文中运行,并且evry无法知道命令是否运行失败 - 如果命令失败,您可以删除标签文件,将其重置为稍后再次运行(因为如果文件不存在,evry假定它是一个新的任务)

evry 2 months -selenium && {
# evry succeeded, so the external command should be run
    python selenium.py || {
        # the python process exited with a non-zero exit code
        # remove the tag file so we can re-try later
        rm "$(evry location -selenium)"
        # maybe notify you that this failed so you go and check on it
        notify-send -u critical 'selenium failed!"
    }
}

持续时间

持续时间(例如,evry 2 months, 5 days)由一个PEG解析,因此它非常灵活。所有这些都是有效的持续时间输入

  • 2月份, 5
  • 2weeks 5hrs(逗号是可选的)
  • 60秒
  • 5周,5天
  • 5weeks, 2weeks(是累加的,所以这将结果是7周)
  • 60sec 2weeks(顺序无关紧要)

请参阅语法了解所有可能的缩写。

这还包括一个duration实用程序命令,用于以秒为单位打印解析的持续时间

$ evry duration 5m
300
$ evry duration 5 minutes
300
$ evry duration 10 days
864000

可以通过EVRY_JSON=1运行来打印更多格式的JSON

示例

这可以用来做任何您可能使用anacron做的事情。例如,定期同步文件

evry 1d -backup && rsync ...

或者,每天缓存命令的输出(例如,我的jumplist


expensive_command_cached() {
	evry 1d -expensive_command_cached && cmd >~/.cache/cmd_output
	cat ~/.cache/cmd_output
}

expensive_command_cached

我有一些定期运行的作业(例如,使用selenium登录某些网站并点击按钮,或者检查我的音乐元数据),这些作业我想定期运行。

将所有我想定期运行的作业放在一个housekeeping脚本中,我每天/每周运行它,这使我能够轻松地监控输出,同时也允许我灵活地安排以不同速率运行任务。这也意味着这些脚本/命令可以提示我输入/确认,因为这是从终端手动运行的,而不是像cron那样在后台运行。

我经常在开发网站时用这个代替cron,例如这里,我使用它定期运行web服务的缓存任务。将它们放在这样的脚本中意味着在开发和部署时具有相同的接口/环境,因此在部署到生产环境时不会出现可能丢失环境变量/位于错误目录的问题,并且很容易在开发时“重置”cron作业。

高级用法

可以将EVRY_DEBUG环境变量设置为提供有关从用户输入中解析的内容以及下一次运行成功之前的时间的信息。

$ EVRY_DEBUG=1 evry 2 months -pythonanywhere && pythonanywhere_3_months -Hc "$(which chromedriver)"
tag_name:pythonanywhere
data_directory:/home/sean/.local/share/evry/data
log:parsed '2 months' into 5184000000ms
log:60 days (5184000000ms) haven't elapsed since last run, exiting with code 1
log:Will next be able to run in '46 days, 16 hours, 46 minutes, 6 seconds' (4034766587ms)

EVRY_PARSE_ERROR_LOG 环境变量可以被设置为将任何持续时间解析错误保存到文件中,这对于调试非常有用,尤其是在您动态生成持续时间字符串时。在您的shell配置文件中

export EVRY_PARSE_ERROR_LOG="$HOME/.cache/evry_parse_errors.log"

如果您想“重置”一个任务,可以这样做:rm ~/./.local/share/evry/data/<标签名称>;删除标签文件。下次运行 evry 时,它会假设这是一个新任务,并成功退出。我使用以下shell函数来“重置”任务

job-reset() {
	local EVRY_DATA_DIR
	EVRY_DATA_DIR="$(evry location - 2>/dev/null)"
	cd "${EVRY_DATA_DIR}"
	fzf -q "$*" -m | while read -r tag; do
		rm -v "${tag}"
	done
	cd -
}

EVRY_JSON 环境变量可以被设置为以更易于消费的格式提供类似信息(例如,使用 jq

例如:./schedule_task

#!/bin/bash

if JSON_OUTPUT="$(EVRY_JSON=1 evry 2 hours -task)"; then
  echo "Running task..."
else
  # extract the body for a particular log message
  NEXT_RUN="$(echo "$JSON_OUTPUT" | jq -r '.[] | select(.type == "till_next_pretty") | .body')"
  printf 'task will next run in %s\n' "$NEXT_RUN"
fi
$ ./schedule_task
Running task...
$ ./schedule_task
task will next run in 1 hours, 59 minutes, 58 seconds

参考:当 evry 失败(命令未运行)时的典型JSON输出

[
  {
    "type": "tag_name",
    "body": "task"
  },
  {
    "type": "data_directory",
    "body": "/home/sean/.local/share/evry/data"
  },
  {
    "type": "log",
    "body": "parsed '2 hours' into 7200000ms"
  },
  {
    "type": "duration",
    "body": "7200000"
  },
  {
    "type": "duration_pretty",
    "body": "2 hours"
  },
  {
    "type": "log",
    "body": "2 hours (7200000ms) haven't elapsed since last run, exiting with code 1"
  },
  {
    "type": "log",
    "body": "Will next be able to run in '1 hours, 58 minutes, 17 seconds' (7097748ms)"
  },
  {
    "type": "till_next",
    "body": "7097748"
  },
  {
    "type": "till_next_pretty",
    "body": "1 hours, 58 minutes, 17 seconds"
  }
]

依赖项

约2.6–3.5MB
约74K SLoC