1

Using

find $dirname -printf "%T@ %Tc %p\n" | sort -n | tail -n 15

I can find the n (in my example n = 15) newest file in a directory structure below $dirname. Now I want to copy (or link) the files into a directory $current (I want to run this as a background script periodically to get a view of all new added files.

The background for this is, that I mirror a directory structure (via rclone) from ms sharepoint to my laptop and then I want to have a directory $current showing me all n latest modified files.

3 Answers 3

1

Try something like the following bash script, run from cron:

#!/bin/bash

# exit on any error
set -e
set -o pipefail

# defaults for the options 
n=15
dirname='/default/path/to/your/files'
current='/default/path/to/the/current/dir'

while getopts ':n:d:c:' opt ; do
  case "$opt" in
    n) n="$OPTARG";;
    d) dirname="$OPTARG";;
    c) current="$OPTARG";;
    :) echo "Error: '-$OPTARG' requires an argument" >&2 ; exit 1 ;;
    *) echo "Error: unknown option '-$OPTARG'" >&2 ; exit 1 ;;
  esac
done

if [ ! -d "$current" ] ; then
  if ! mkdir -p "$current" ; then
    echo "Error: '$current' can't be created or already exists and isn't a directory" >&2
    exit 1
  fi
fi

rm -f "$current"/*
# alternatively, to delete only symlinks in $current:
# find -H "$current/" -maxdepth 1 -type l -delete

find "$dirname/" -type f -printf "%T@\t%p\0" | 
  sort -z -r -n |
  head -z -n "$n" |
  sed -z -e 's/^[^\t]*\t//' |
  xargs -0r readlink -e -z |
  xargs -0r ln -s -t "$current/"

This uses NUL as the separator between filenames so it will work with filenames containing any valid characters (NUL is the only character which isn't valid in a path/filename).

The sed script in the pipeline removes the timestamp field (everything up to and including the first TAB character - necessary because TAB is a valid character in filenames). Instead of sed, you could use cut -z -f2- here.

I've used sort -r -n and head -n instead of tail -n because it will be slightly faster - head can exit as soon as it has output the first 15 filenames, while tail has to read the entire input.

xargs -0r is used twice. First to use readlink to get the full canonical path of each filename, and then to make the symlinks in $current with ln.

NOTE: This requires versions a version of find that supports printf, and versions of sort, head, readlink, and sed which support -z for NUL-separated input/output, and a version of ln that supports the -t target directory option, and a version of xargs which supports -0. e.g. GNU versions of all of those.

Also, I can't remember if \t for tab works inside a bracket expression in other versions of sed, but it certainly works in GNU sed.

If you're running Linux, then GNU versions of coreutils, findutils, sed, etc will be what you have (unless you've done something really weird and inexplicably crazy like installing non-GNU versions).

3
  • BTW, if you have the moreutils package installed, you could use /usr/bin/errno from that to print a more detailed error message if mkdir fails. errno's output is based on the standard system error codes. Standard tools like mkdir and ln print fairly useful error messages, though, so that's really only useful for programs that don't do that or if you've suppressed the normal error output for some reason.
    – cas
    Commented Sep 4, 2021 at 8:47
  • Thank you very much. Now it works. I will delete my "debug" comments.
    – student
    Commented Sep 4, 2021 at 10:35
  • Follow up question: unix.stackexchange.com/questions/667589/…
    – student
    Commented Sep 4, 2021 at 10:53
1

Many complex find invocations translate to something cryptic but short in zsh. Custom sorting is especially convenient in zsh compared to other means. The following zsh command lists the 15 newest regular files below $dirname:

print -rC1 -- $dirname/**/*(.Nom[1,15])

** does recursive globbing. The characters in parentheses at the end are glob qualifiers: . selects regular files, N prevents an error if there are no matching files, om sorts by modification time (newest first), and [1,15] selects the first 15 files.

Since this is a wildcard expansion into a command line, you don't have to worry about shell special characters in file names. You do have to worry about leading dashes being interpreted as options if $dirname starts with a -.

For example, here's simple (untested) code to update a directory recent containing symbolic links to the most recent files under everything. The code adds a suffix to file names when there are name conflicts.

#!/usr/bin/env zsh
set -e
recent=(/everything/**/*(.Nom[1,15]))
rm -f /recent/*(@N)
for file in $recent; do
  target=/recent/$file:t
  i=1
  while [[ -e $target ]]; do
    target=$file:t:r-$i
    if [[ -n $file:e ]]; then target+=.$file:e; fi
    ((++i))
  done
  ln -s -- $file $target
done

The suffixes :t, :r, :e are history expansion modifiers that extract the base name (tail), name without extension (root) and (last) extension from a file name respectively.

1
  • 1
    With zsh and GNU ln, see also ln -rs --backup=numbered /everything/**/*(.om[1,15]) /recent/ Commented Sep 4, 2021 at 20:19
0

Put thoses commands in a script file:

#! /bin/bash

current="/your/absolute/path"
rm -rf "${current}"
mkdir -p "${current}"
while read FILENAME; do
    ln -s "${FILENAME}" "${current}"
done < <(find "${current}" -printf "%T@ %p\n" | sort -n | tail -3 | cut -d " " -f 2-)

Then:

Add execution of this script in your crontab.

You must log in to answer this question.

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