3

In a directory, I have a number of files, and I need to do something with the most recently modified file with a .txt suffix.

Ideally, I'd like to do

myutility "$newest"

in the end, where $newest would be the pathname of the most recently modified file.

It would be nice if this additionally could be generalised so that I could get the most recently modified file with a .txt suffix in a directory hierarchy, and even better if I could get the, say, five most recently modified files so that I could use

myutility "${newest[@]}"

to run my utility on the five most recently modified .txt files in a whole directory hierarchy.

A solution using bash or ksh93 would be best.

3 Answers 3

4

Although you tagged the question bash and ksh, with zsh and glob qualifiers:

print **/*.txt(.om[1,5])

prints the first 5 plain files (.) with .txt extension, ordered by ascending modification time (om)

ex.

print **/*.txt(.om[1,5])
dir/file1.txt file2.txt dir/file.txt File1.txt File2.txt
2
  • I should really read up on the zsh shell's operators etc. someday soon... How would you go about getting the results into an array? I realise that just using the expansion on the command line of a utility would work, but just for completeness... Same syntax as bash and ksh93?
    – Kusalananda
    Jul 18, 2018 at 12:54
  • @Kusalananda yes I believe you can do a ksh/bash style newfiles=( **/*.txt(.om[1,5]) ) in zsh (although that may not be the recommended way - I'm just feeling my way into zsh myself) Jul 18, 2018 at 12:59
3

You can list the files by modification time (or creation or access) with ls.

simply say

ls -t *.txt | head -n5 

to get the last 5 recent .txt files

You can feed it into myutility with $() or xargs.

ls -t *.txt | head -n5 | xargs myutility

If you need the recursion, you can use find.

find -type f -iname "*.txt" | xargs ls -t | head -n5 | xargs myutility

(possible issue: you have to have at least one .txt file for this to work. you can fix it, but it's the cleanest solution).

EDIT: OP wanted to know how to handle filenames with spaces. Here is one solution:

find -type f -iname "*.txt" | xargs ls -t | head -n5 | tr '\n' '\0' | xargs -0 myutility

If you want to solve the possible issue mentioned above you can check for the files before-head with find ..... | egrep '.*' || exit or simply include some nonsensical filename to ls and ignore the warning like xargs ls -t ''.

6
  • This requires that the filenames do not contain whitespace characters such as space or newline. It also requires GNU find.
    – Kusalananda
    Jul 18, 2018 at 12:07
  • of course. I wrote it this way for simplicity. You can easily fix it by using zero delimiter. option -0 for xargs and print0 for find.
    – goteguru
    Jul 18, 2018 at 12:09
  • 1
    Good people never use whitespace in filenames anyway... :-D
    – goteguru
    Jul 18, 2018 at 12:09
  • I don't think I mentioned anything about the character set used for the filenames in the question, apart from them ending with .txt. So there can be no assumptions made. Good people write code to support any valid Unix filename. Using -print0 and -0 with find and xargs will help partway through the pipeline (if you have tools that support these options), but you'll have the same issue when you get to ls and head later, especially if the filenames contain newlines.
    – Kusalananda
    Jul 18, 2018 at 12:16
  • no insult was intended. I hoped you are here to learn, and simple is better than complex if you want to learn. Extended solution included for you.
    – goteguru
    Jul 18, 2018 at 12:28
0

It may look ugly but using find to get files is the most robust method, avoiding issues with spaces & other special characters in names. You get a list of modified times + files, sort it by recent, get the top n (here 5), drop the times leaving only filenames, and pass it to your script:

find dir -iname '*.txt' -printf '%T@\t%p\0' | sort -znr | head -z -n 5 | grep -zPo '^\S+\s+\K[\S\s]*' | xargs -0r myutility

If you're on an older version of coreutils (< v8.25 head won't support -z) or grep (< v2.25 has a bug), you can use a script:

#!/bin/bash
flimit=5
filenames=()
while (( flimit-- )) && IFS=$'\t' read -r -d $'\0' mtime filename; do
    filenames=("${filenames[@]}" "$filename")
done < <(find -name '*.txt' -printf '%T@\t%p\0' | sort -znr)
if [[ ${#filenames[@]} -gt 0 ]]; then
    myutility "${filenames[@]}"
else
    >&2 echo no files found
fi

You must log in to answer this question.

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