How to get the last N files in a directory?

This can be easily done with bash/ksh93/zsh arrays:

a=(*)
cp -- "${a[@]: -4}" ~/

This works for all non-hidden file names even if they contain spaces, tabs, newlines, or other difficult characters (assuming there are at least 4 non-hidden files in the current directory with bash).

How it works

  • a=(*)

    This creates an array a with all the file names. The file names returned by bash are alphabetically sorted. (I assume that this is what you mean by "ordered by file name.")

  • ${a[@]: -4}

    This returns the last four elements of array a (provided the array contains at least 4 elements with bash).

  • cp -- "${a[@]: -4}" ~/

    This copies the last four file names to your home directory.

To copy and rename at the same time

This will copy the last four files only to the home directory and, at the same time, prepend the string a_ to the name of the copied file:

a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done

Copy from a different directory and also rename

If we use a=(./some_dir/*) instead of a=(*), then we have the issue of the directory being attached to the filename. One solution is:

a=(./some_dir/*) 
for f in "${a[@]: -4}"; do cp "$f"  ~/a_"${f##*/}"; done

Another solution is to use a subshell and cd to the directory in the subshell:

(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done) 

When the subshell completes, the shell returns us to the original directory.

Making sure that the ordering is consistent

The question asks for files "ordered by file name". That order, Olivier Dulac points out in the comments, will vary from one locale to another. If it is important to have fixed results independent of machine settings, then it is best to specify the locale explicitly when the array a is defined. For example:

LC_ALL=C a=(*)

You can find out which locale you are currently in by running the locale command.


If you are using zsh you can enclose in parenthesis () a list of so called glob qualifiers which select desired files. In your case, that would be

cp *(On[1,4]) ~/

Here On sorts file names alphabetically in reverse order and [1,4] takes only first 4 of them.

You can make this more robust by selecting only plain files (excluding directories, pipes etc.) with ., and also by appending -- to cp command in order to treat files which names begin with - properly, so:

cp -- *(.On[1,4]) ~

Add the D qualifier if you also want to consider hidden files (dot-files):

cp -- *(D.On[1,4]) ~

Here is a solution using extremely simple bash commands.

find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done

Explanation:

find . -maxdepth 1 -type f

Finds all files in current directory.

sort

Sorts alphabetically.

tail -n 4

Only show last 4 lines.

while read -r file; do cp "$file" ~/; done

Loops over each line performing the copy command.