#!/bin/sh # history # 2018-02-10 initial version # 2020-06-10 added -a option # ????-??-?? gzip result # 2021-03-22 put current date into output filename # 2021-04-06 code refactoring with more functions and input sanitisation # 2021-12-22 prefer zstd over gzip # 2022-01-23 don't create symlink if there is no previous file # 2022-01-30 added xz to compressors # 2022-12-08 some cleanup, added -C and -K options die() { echo "${@}" > /dev/stderr exit 1 } usage() { cat <<-EOF Usage: $(basename "$0") [-o NAME] [DIR] A wrapper to tree, it writes the content of DIR into a text file. The file is named after DIR and the current date, and symlink to the most recent version is set. The file is automatically compressed to zstd, xz or gzip, whichever is available in that order. Options: -a access attributes: owner, group, permissions -C do not compress result file -K do not keep backup of existing file in case of overwriting -o The destination where to write the trees. Default: . If it is a directory: write in there If it is a filename: use that as base. If not, use the name of the directory as name base. EOF } test_writable() { touch "$1" 2>/dev/null || die "Cannot create file in $(dirname "$1")." } # run tree and redirect output to destination file call_to_tree() { WHICH="$1" OUTPATH="$2-$1" shift 2 declare TREE_ARGS TREE_ARGS+=("$@") local DATED_PATH="$OUTPATH-$TODAY" local EXT local PACK local CREATE_SYMLINK=no if [ "$COMPRESS" = "no" ]; then TREE_ARGS+=("-o") TREE_ARGS+=("$DATED_PATH") elif command -v zstd > /dev/null; then PACK="zstd --rm -q -13" EXT=".zst" TREE_ARGS+=("-o") TREE_ARGS+=("$DATED_PATH") elif command -v xz > /dev/null; then PACK="xz" EXT=".xz" TREE_ARGS+=("-o") TREE_ARGS+=("$DATED_PATH") else PACK="gzip" EXT=".gz" fi ls "$OUTPATH-*" > /dev/null 2>&1 && CREATE_SYMLINK=yes # pack yet unpacked file if [ "$COMPRESS" != "no" ]; then [ -f "$DATED_PATH" ] && [ ! -f "$DATED_PATH$EXT" ] && $PACK "$DATED_PATH" fi # move away old file if [ -f "$DATED_PATH$EXT" ]; then [ "$KEEP_OLD" = "no" ] && rm -f "$DATED_PATH$EXT" || mv -f "$DATED_PATH$EXT" "$DATED_PATH.old$EXT" fi test_writable "$DATED_PATH" echo "Writing $WHICH tree to $(realpath "$DATED_PATH")$EXT" BEGIN_TIME=$(date +%s) "${TREE_ARGS[@]}" END_TIME=$(date +%s) echo >> "$DATED_PATH" df -BM . | awk 'NR==2 { print "Size: " $3 " Available: " $4 " Usage: " $5; }' >> "$DATED_PATH" [ "$PACK" ] && $PACK "$DATED_PATH" if [ "$CREATE_SYMLINK" = yes ]; then if [ -e "$OUTPATH" ] && [ ! -L "$OUTPATH" ]; then echo "Cannot set symlink, filename already taken." else echo "Setting symlink $OUTPATH -> $(basename "$DATED_PATH")" ln -sfn "$(basename "$DATED_PATH$EXT")" "$OUTPATH$EXT" fi fi echo "Reading of directory took $((END_TIME - BEGIN_TIME)) seconds." } # parse arguments unset ACCESSRIGHTS unset COMPRESS unset KEEP_OLD while getopts "CKaho:" OPTION do case $OPTION in C) COMPRESS=no ;; K) KEEP_OLD=no ;; a) ACCESSRIGHTS=yes ;; h) usage; exit 0 ;; o) OUTARG="$OPTARG" ;; esac done shift $((OPTIND-1)); OPTIND=1 # input sanitisation and derivation of destination paths DIR="${1:-$(pwd)}" [ -d "$DIR" ] || die "The given directory does not exist." [ "$OUTARG" ] && OUTARG="$(realpath "$OUTARG")" || OUTARG="$(pwd)" if [ -d "$OUTARG" ]; then OUTDIR="$OUTARG" OUTNAME="$(basename "$(realpath "$DIR")")" # realpath to catch `videotree .` [ "$OUTNAME" = "/" ] && OUTNAME=root else OUTDIR="$(dirname "$OUTARG")" OUTNAME="$(basename "$OUTARG")" fi TODAY="$(date +%F)" cd "$DIR" # write a very verbose file with permission and owner information call_to_tree "detailed" "$OUTDIR/$OUTNAME" tree -af -DFins --dirsfirst --du --timefmt "%Y-%m-%d %T" ${ACCESSRIGHTS:+-pug} # write a smaller version with only file size information #call_to_tree "simple" "$OUTDIR/$OUTNAME-tree" tree -ax -n --du -h --dirsfirst echo "Done."