1

Want a solution for managing temporary files in a Linux environment with a Bash script.

  • If files are in a root folder and remain unmodified for 10 days, move the files to a subfolder and add a timestamp prefix (e.g., "2022-01-01_filename").
  • If a previously moved file is modified, it should be returned to the root folder without the timestamp prefix.
  • Remove files not modified for 90 days from their last modification date.

This is what I've tried. It was giving me a few errors, but after I fixed those, it doesn't seem to do anything. Anyways, after thinking about this for a couple of hours, I thought I would be better off just using a file manager that can sort by modification date.

#!/bin/bash

FOLDER="$HOME/tmp"
SUBFOLDER="$HOME/tmp/old"

[ ! -d "$FOLDER" ] && mkdir -p "$FOLDER"
[ ! -d "$SUBFOLDER" ] && mkdir -p "$SUBFOLDER"

find "$FOLDER" -maxdepth 1 -type f -mtime +10 -exec sh -c '
  for file do
    timestamp=$(date +"%Y-%m-%d")
    new_filename="$SUBFOLDER/$timestamp_$(basename "$file")"
    mv "$file" "$new_filename"
  done' sh {} +

find "$SUBFOLDER" -type f -mtime -10 -exec sh -c '
  for file do
    new_filename="$FOLDER/${file#*_}"
    mv "$file" "$new_filename"
  done' sh {} +

find "$SUBFOLDER" -type f -mtime +90 -exec rm {} \;
chmod +x /path/to/script.sh
crontab -e
0 0 * * * /path/to/script.sh
2
  • The echo was the first thing I did but it's using another shell. It doesn't do anything when running it myself either. Commented Nov 4, 2023 at 9:09
  • I refer to -exec sh -c Commented Nov 4, 2023 at 9:18

1 Answer 1

0

There are several problems (some of which minor) with that code:

  • you don't export those FOLDER and SUBFOLDER variables to the environment so they won't be available in the sh invocations started by find.
  • a ~/tmp directory should be private, should rather be mkdir -m 700 ~/tmp.
  • The [ -d ... ] || mkdir ... is typical TOCTOU race pattern. The prior check is not needed for mkdir -p.
  • $(...) strips trailing newline characters so can't be used for arbitrary file names
  • you don't do any error handling. You could at least report whether there's been an error in the exit status.
  • you don't need to compute today's date at each iteration
  • Note that -mtime +10 is for files 11 days old or older and -mtime -10 is for files less than 10 days old, so there's a 24 hour period of files not covered here (those last modified between 10 and 11 days ago).
  • in your second find command, if there are files without _ in their name or $SUBFOLDER contains _ characters, that won't work as expected. For instance if $file is /home/you/tmp/old/old-file, $new_file_name will become /home/you/tmp//home/you/tmp/old/old-file and for /home/your_home/tmp/old/2023-10-01_file, that gives /home/your_home/tmp/home/tmp/old/2023-10-01_file.
  • conflicts, like when the destination files of the mv already exist (possibly even as a directory or symlink to directory) are not handled.
  • -maxdepth is not a standard find predicate.

With zsh:

#! /bin/zsh -
autoload zmv || exit
mkdir -pm 700 -- ~/tmp && cd -P -- ~/tmp && mkdir -p old || exit

today=${(%):-%D{%F}} ret=0

# move 10 day old or older regular files
zmv '*(#qND.m+9)' 'old/${today}_$f' || ret=$?

# move back regular files that are less than 10 day old.
zmv 'old/<1900-2100>-<1-12>-<1-31>_(*)(#qND.m-10)' '$1' || ret=$?

# remove regular files that are 91 day old or older
rm -f old/*(ND.m+90) || ret=$?

exit $ret

With sh (no point in using bash as bash has no extension over sh that would help here; nothing in your own code is bash specific either and you do give a .sh extension to your script instead of .bash):

#! /bin/sh -
mkdir -pm 700 -- ~/tmp && cd -P -- ~/tmp && mkdir -p old || exit

TODAY=$(date +%Y-%m-%d) || exit
export TODAY

ret=0
# move 10 day old or older regular files
find . ! -name . -prune -type f -mtime +9 -exec sh -c '
  ret=0
  for file do
    file=${file#./}
    mv -i -- "$file" "old/${TODAY}_$file" || ret=$?
  done
  exit "$ret"' {} + || ret=$?

# move back regular files that are less than 10 day old.
LC_ALL=C find old/. ! -name . -prune \
  -name '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_*' \
  -type f -mtime -10 -exec sh -c '
  ret=0
  for file do
    new=${file#*/./}
    new=${new#*_}
    if [ -d "$new" ]; then
      printf>&2 "%s\n" "$new is a directory"
      ret=1
    else
      mv -i -- "$file" "$new" || ret=$?
    fi
  done
  exit "$ret"' {} + || ret=$?

# remove regular files that are 91 day old or older
find old/ -mtime +90 -type f -exec rm -f {} + || ret=$?

exit "$ret"

The -i option of mv used here to handle conflicts. If run interactively, you'll be prompted when a file would be overwritten. If run by cron where stdin is /dev/null, that will refuse do overwrite, but unfortunately not return with failure. See also the -n option of GNU mv (doesn't return failure either though).

Also mv a b will move a into b instead of renaming it to b if b exists as a directory (or symlink to directory). Hence the [ -d check beforehand, but that also introduces a TOCTOU race; the GNU implementation of mv has a -T option which helps. zsh's zmv has similar race conditions in its conflict handling; passing -o -nT to zmv on GNU system can help alleviate at least some of them.

(note: I've not tested any of that code).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .