#!/usr/bin/env bash set -e ! read -rd '' FILTER_AWK <<'EOF' function flr(n, s) { return int(n / s) * s } BEGIN { len = split("h d w m", arr) for (i = 1; i <= len; ++i) { b = arr[i] if (!match(opt_retain, "([*0-9]+)" b, md)) continue ret[b] = md[1] buckets[++n_buckets] = b newest_in[b] = !!index(opt_newest_in, b) } split("sun mon tue wed thu fri sat", arr) for (i in arr) arr2[arr[i]] = i - 1 wshift = (11 - arr2[opt_week_start])*24*60*60 } { t = mktime(gensub(/(.*\/|^)(.+)-(..)-(..)-(..)(..)(..)/, "\\2 \\3 \\4 \\5 \\6 \\7", 1), 1) bt["h"] = flr(t, 60*60) bt["d"] = flr(t, 24*60*60) bt["w"] = flr(t + wshift, 7*24*60*60) - wshift bt["m"] = mktime(strftime("%Y %m", t, 1) " 01 00 00 00", 1) for (i = 1; i <= n_buckets; ++i) { b = buckets[i] if (b == "m" && !newest_in["w"] && bt["m"] > bt["w"]) next if (newest_in[b] && bt[b] == last[b, b]) { for (j = 1; j < i; ++j) { if (bt[buckets[j]] == last[b, buckets[j]]) break } if (j == i) next } if ((b, bt[b]) in bkeep || ret[b] == "*" || bc[b]++ < ret[b]) bkeep[b, bt[b]] = $1 for (j in buckets) last[b, buckets[j]] = bt[buckets[j]] } } END { for (i in bkeep) ++keep[bkeep[i]] asorti(keep) for (i in keep) print keep[i] } EOF run() { echo "* ${@}" "${@}" } backup() { dest_dir="${1}" src_path="${2}" retain="${3}" newest_in="${4}" week_start="${5}" [[ "$((dcount++))" -ne 0 ]] && echo echo "[${dcount}] [${retain}] ${src_path} => ${dest_dir}" mapfile -t snaps < <(shopt -s nullglob; printf '%s\n' "${dest_dir}/"* | sort -r | head -c -1) dest_path="${dest_dir}/$(date -u '+%Y-%m-%d-%H%M%S')" run rsync -ac --mkpath ${snaps[0]:+"--link-dest=${snaps[0]}"} \ "${src_path}" "${dest_path}" snaps=("${dest_path}" "${snaps[@]}") mapfile -t keep < <( awk \ -v "opt_retain=${retain}" \ -v "opt_newest_in=${newest_in}" \ -v "opt_week_start=${week_start}" \ "${FILTER_AWK}" < <(printf '%s\n' "${snaps[@]}")) if [[ -z "${keep}" ]]; then echo "WARNING: dirs to keep shouldn't be empty; skipping" >&2 return fi for ((i = ${#snaps[@]} - 1, ki = 0; i >= 0; --i)); do if [[ "${snaps[i]}" == "${keep[ki]}" ]]; then ((++ki)) else run rm -r "${snaps[i]}" fi done printf 'kept %s\n' "${keep[@]}" } declare -A def_opts=( ['retain']='*h*d*w*m' ['newest_in']='h' ['week_start']='sun' ) declare -A opts while IFS='=' read -r f1 f2; do [[ -z "${f1}" ]] || [[ "${f1}" == '#'* ]] && continue if [[ "${f1}" =~ ^\[(.*)]$ ]]; then base_path="${BASH_REMATCH[1]}" declare -A opts="$(declare -p def_opts | sed '1s/[^=]*=//')" elif [[ "${f1}" == '$'* ]]; then opt="${f1:1}" [[ "${f2}" == '-' ]] && opts["${opt}"]= || opts["${opt}"]="${f2:-"${def_opts["${opt}"]}"}" else backup "${base_path}/${f1}" "${f2}" \ "${opts['retain']}" \ "${opts['newest_in']}" \ "${opts['week_start']}" fi done < "${1}"