1. Overview

Copying the latest file from one directory to another can be a frequent task when archiving or performing backups.

In this tutorial, we’ll discuss several methods to accomplish the task using available command-line tools in Linux.

2. Sample Task

Let’s suppose our current directory, dir, contains three files named file1, file2, and file3, in addition to two subdirectories named dir1 and dir2:

$ > file1
$ > file2
$ > file3
$ mkdir dir1
$ mkdir dir2

The files and subdirectories are created sequentially as shown above. Therefore, their modification timestamps will reflect this fact.

Using ls, we can list the objects in the current directory sorted by modification time:

$ ls -Alt
total 8
drwxr-xr-x 2 sysadmin sysadmin 4096 Oct 28 12:48 dir2
drwxr-xr-x 2 sysadmin sysadmin 4096 Oct 28 12:48 dir1
-rw-r--r-- 1 sysadmin sysadmin    0 Oct 28 12:48 file3
-rw-r--r-- 1 sysadmin sysadmin    0 Oct 28 12:48 file2
-rw-r--r-- 1 sysadmin sysadmin    0 Oct 28 12:48 file1

The -A option used with ls shows all objects including hidden ones, except for the . and .. directories. The -l option is for using the long listing format to show information about each object. Finally, the -t option is for sorting the objects by modification time, with the newest appearing on top.

We notice, in this case, that dir2 is the latest object, followed by dir1, file3, file2, and finally file1.

Our objective is to automatically identify and copy the latest regular file, which is file3, from the current directory to another directory, say, dir2.

3. Using find

The find command can be useful for filtering files based on their type. We can use the find command to identify regular files and sort them by modification time:

$ find . -maxdepth 1 -type f -exec ls -t {} +
./file3  ./file2  ./file1

The -maxdepth option used with find specifies the maximum depth or level of directories to search in. The -type option specifies the type of files to search for, which in this case, are regular files. Finally, the -exec option allows executing a specific command over the found files.

The command we execute with the -exec option is ls -t which lists the files sorted by modification time. The + sign appearing at the end is for appending all the found files before executing the ls -t command, which is run only once.

Next, we can pipe the output to the head -1 command to extract only the first file, that is, the latest one. Then, we use the cp command to copy the file to the destination directory:

$ cp "$(find . -maxdepth 1 -type f -exec ls -t {} + | head -1)" ./dir2

Importantly, we run the find and head commands within a subshell, and then we use cp to copy the found file to dir2.

We can check the result by listing the contents of dir2:

$ ls ./dir2
file3

We notice that file3, which is the most recent file, was copied to the required destination.

Notably, if the source directory doesn’t contain any regular files, the result of the command substitution is a null string, and cp returns an error. Therefore, in such a case, no file is copied to the destination directory.

4. Using ls

Alternatively, we can accomplish the task using the ls command instead of find.

First, we’ll need to use the -F option with ls:

$ ls -AFt
dir2/  dir1/  file3  file2  file1

The -F flag appends an indicator symbol to the end of objects, depending on their type. In particular, directory names are followed by a forward slash (/).

This way, we can exclude directories with the help of grep -v:

$ ls -AFt | grep -v '/$' | head -1
file3

We pipe the output of ls to grep -v which excludes objects that match the specified /$ regex pattern. In other words, we exclude every object with a name that ends with a forward slash. We then pipe the result to head -1 to extract only the first, most recent, file.

Notably, we don’t need to use the -1 option with ls for displaying one file per line because this is done automatically when the output of ls is piped to another command.

We execute these commands within a subshell and use cp to copy the extracted file to dir2:

$ cp "$(ls -AFt | grep -v '/$' | head -1)" ./dir2

This copies file3 into ./dir2.

5. Filenames Containing Newline Characters

The methods discussed so far work as expected as long as the filenames don’t contain newline characters. Unfortunately, it can happen, though rarely, that a filename does contain a newline character.

Let’s first add a new file with a name that contains a newline character to the current directory:

$ > 'file '$'\n''4'

Let’s list all objects sorted by modification time in the current directory:

$ ls -1AFt
'file '$'\n''4'
dir2/
dir1/
file3
file2
file1

We see that the latest file added shows as the first in the list. To automatically identify the file and copy it to its destination, we can first cd to the parent directory and then use a Bash script:

$ cd ..
$ cat copy_latest_file.sh
#!/usr/bin/env bash
source_directory='./dir'
destination_directory="./dir/dir2"

latest_file=""

while IFS= read -r -d $'\0' file; do
    if [[ -z "$latest_file" || "$file" -nt "$latest_file" ]]; then
        latest_file="$file"
    fi
done < <(find "$source_directory" -maxdepth 1 -type f -print0)

if [[ -n "$latest_file" ]]; then
    cp "$latest_file" "$destination_directory"
    echo "Copied latest file from $source_directory to $destination_directory"
else
    echo "No files found in $source_directory"
fi

The copy_latest_file.sh script implements several steps:

  1. use a shebang directive on the first line to indicate a Bash script
  2. set the source_directory and destination_directory variables
  3. initialize the latest_file variable to the null string
  4. use find to identify regular files in the source directory and use the -print0 option to print these with the null character (\0) as a separator
  5. use process substitution around the find command as input to a while loop
  6. the while loop iterates over each file as it reads it from find using the read command with the delimiter set to the null character
  7. if the latest_file variable is the null string or if the current file is newer than that in the latest_file variable, then update the latest_file to the current file; repeat until the while loop exits
  8. if the latest_file variable is non-empty, then copy it to the destination directory and print a message that it has been copied, otherwise print a message that no files were found

Let’s grant the script execute permissions using chmod:

$ chmod u+x copy_latest_file.sh

Next, let’s run the script:

$ ./copy_latest_file.sh
Copied latest file from ./dir to ./dir/dir2

Finally, let’s check the contents of dir2:

$ ls ./dir/dir2
 file3  'file '$'\n''4'

We see that the directory now contains the new file in addition to the previously copied file3.

6. Conclusion

In this article, we explored different ways to copy the latest file from one directory to another. In particular, we can use find as well as ls to identify the latest file and then copy it with cp. We also saw how to use a Bash script for the more general case when files can contain newline characters in their name.

Comments are closed on this article!