0

I would like to parse the n most recent files in a directory with a for loop. I thought about using ls -t piped to a head -n<number_of_files> but it is not working since I use files with whitespaces.

How should I modify the following script to parse only the n most recent files:

[ -n "$BASH_VERSION" ] && shopt -s nullglob

for f in *; do
    [ -e $f ] && echo $f || continue
done

I am looking for a sh compatible solution.

  • What is your `bash` version? (output of `bash --version`) – Inian Jun 19 '20 at 09:02
  • 1
    Is this just for `bash` or should it be `sh` compatible too? Are you running on Linux with GNU tools, or some other platform? – roaima Jun 19 '20 at 09:31
  • Is zsh available? – Jeff Schaller Jun 19 '20 at 10:24
  • 1
    This is pretty much trivial if the files are named so that the default sort by name puts them in timestamp order. And a huge pain to do in Bash or sh if they don't, and you need to care about stuff like newlines in the filenames. Zsh would make it easy, though, e.g. https://unix.stackexchange.com/a/229956/170373 There's probably an answer here on how to do it in sh or Bash, but I can't find it just now – ilkkachu Jun 19 '20 at 11:48
  • 1
    Oh, here's some: [How I delete all files in a directory with specific extensions except the last 5 of them](https://unix.stackexchange.com/q/355857/170373) and [Delete all files in a folder except the last (most recent) 20](https://unix.stackexchange.com/q/239686/170373) – ilkkachu Jun 19 '20 at 11:53

1 Answers1

2

You've a check for $BASH_VERSION being (un)set which, to me, suggests you might be considering running this script under sh rather than bash. To that end we can omit nullglob because the code needs to check for this case anyway.

n=10    # Process no more than this number of files

k=0
for f in *
do
    [ -f "$f" ] || continue
    k=$((k+1))

    # Do stuff with your files as "$f"
    :

    [ $k -ge $n ] && break
done
echo "Files processed: $k"

You can make a couple of small changes if you're going to be using just bash

  • use [[ ... ]] instead of [ ... ]
  • replace k=$((k+1)) with ((k++))

Notice I've double-quoted my string variable everywhere I've used it. This protects its value from being parsed by the shell and split into words on whitespace. (In general, it's better to double-quote variables when you use them.)


I've re-read your question and realised you want the N most recently modified files. If you were using GNU tools I would use them to generate the list of files safely, but you have not specified these so I've resorted to piping from ls. In general this is not good practice, mainly because file names can contain non-printing characters and even newlines, but I don't know of a better non-GNU solution without dropping into zsh.

n=10    # Process no more than this number of files

k=0
ls -t |
    while IFS= read -r f
    do
        [ -f "$f" ] || continue
        k=$((k+1))

        # Do stuff with your files as "$f"
        :

        [ $k -ge $n ] && break
    done

With GNU tools I'd use this somewhat convoluted pipeline

find -maxdepth 1 -type f -printf "%T@\t%p\0" |    # Prefix numeric timestamp
    sort -z -k1,2rn |                             # Sort by the timestamp
    cut -z -f2- |                                 # Discard the timestamp
    head -z -n$n |                                # Keep only the top N files
    while IFS= read -r -d '' f
    do
        # Do stuff with your files as "$f"
        :
    done
roaima
  • 107,089
  • 14
  • 139
  • 261