Get most recent file in a directory on Linux
Looking for a command that will return the single most recent file in a directory. Not seeing a limit parameter to ls .
Most answers here parse the output of ls or use find without -print0 which is problematic for handling annoying file-names. Always useful to mention: BashFAQ099 which gives a POSIX answer to this problem
@kvantour , I had the same concern. However, the answer from @Stephane_Chazelas uses -printf with \0 at the end of the output — basically making it a formatted -print0 . For me, it’s a lot easier — or at least more elegant — to put the newest (or oldest) filename into a string using what @Stephane_Chazelas shared rather than using BashFAQ/099. Any comments on this are welcome . in the chat. (P.S. BashFAQ/099 is a great answer and something that I think everyone should know about, by the way.)
23 Answers 23
This will return the latest modified file or directory. Not very elegant, but it works.
-A list all files except . and ..
-r reverse order while sorting
-t sort by time, newest first
A minor issue: This version pipes all the output of ls to tail , then prints only the LAST line. IMHO it is better to sort in ascending order and use head instead, as chaos suggested. After printing the first line head quits, so sending the next line (actually next block) will rise a SIGPIPE and ls will quit as well.
Note that this solution includes directories as well as files. This could be a good thing or a bad thing; it depends on what the individual user wants. The reason I bring this up is that the original question says «file».
I think that the intended benefit of using tail instead of head may be to exclude the inclusion of the line describing total returned by the ls.
This command actually gives the latest modified file or directory in the current working directory.
@Josir This can be modified to include the datestamp with ls -tl | head -n 1 , and it doesn’t have to push the whole table through the pipe the way mine does.
This returns a folder if it’s the latest modified, use: ls -Art | head -n1 if you specifically want the latest modified file
This is a recursive version (i.e. it finds the most recently updated file in a certain directory or any of its subdirectory)
find /dir/path -type f -printf "%T@ %p\n" | sort -n | cut -d' ' -f 2- | tail -n 1
Brief layman explanation of command line:
- find /dir/path -type f finds all the files in the directory
- -printf «%T@ %p\n» prints a line for each file where %T@ is the float seconds since 1970 epoch and %p is the filename path and \n is the new line character
- for more info see man find
- NOTE: -f 2 would print only the second token
Here’s a solution for Mac: find $DIR -type f -exec stat -lt «%Y-%m-%d» <> \+ | cut -d’ ‘ -f6- | sort -n | tail -1
Problem is the cut command truncates paths with spaces instead of -f 2 use -f2- to return fields 2 to the end of the line
As in the suggestion by chaos, if you reverse the sort by using sort -nr , you can use head -n 1 instead of tail -n 1 and improve efficiency slightly. (Although if you’re sorting a recursive find, getting the first or last line won’t be the slow part.)
Very useful! I find it more friendly if piped to ls though: find ./ -type f -printf «%T@ %p\n» -ls | sort -n | cut -d’ ‘ -f 2- | tail -n 1 | xargs -r ls -lah
@user’s Mac version only sorts/displays date, not time. Here’s a fixed version: find $DIR -type f -exec stat -lt «%F %T» <> \+ | cut -d’ ‘ -f6- | sort -n | tail -1
Since the newline character is as valid as any in a file name, any solution that relies on lines like the head / tail based ones are flawed.
With GNU ls , another option is to use the —quoting-style=shell-always option and a bash array:
eval "files=($(ls -t --quoting-style=shell-always))" (($ > 0)) && printf '%s\n' "$"
(add the -A option to ls if you also want to consider hidden files).
If you want to limit to regular files (disregard directories, fifos, devices, symlinks, sockets. ), you’d need to resort to GNU find .
With bash 4.4 or newer (for readarray -d ) and GNU coreutils 8.25 or newer (for cut -z ):
readarray -t -d '' files < <( LC_ALL=C find . -maxdepth 1 -type f ! -name '.*' -printf '%T@/%f\0' | sort -rzn | cut -zd/ -f2) (($<#files[@]>> 0)) && printf '%s\n' "$"
Best here would be to use zsh and its glob qualifiers instead of bash to avoid all this hassle:
Newest regular file in the current directory:
Check file age after symlink resolution:
Also, with the completion system ( compinit and co) enabled, Ctrl+X m becomes a completer that expands to the newest file.
Would make you edit the newest file (you also get a chance to see which it before you press Return ).
For the second-newest file.
for the newest regular file (not directory, nor fifo/device. ), and so on.
I like *(.om[1]) , but I usually want to find the newest file in a set of folders, ie. /path/to/folders*/*(.om[1]) , which unfortunately only returns the newest file in all matched folders. See unix.stackexchange.com/questions/552103/… on how to accomplish it over multiple folders.
@SergioAraujo, c like find ‘s -ctime is for the inode change time which has nothing to do with the creation time. The mtime can be seen as the creation time of the file’s contents (as those are never created in one go). Some systems and filesystems record a birth*/*creation time which is the time a file’s inode spawns (possibly again) into existence, but there’s no portable API to retrieve that, and zsh has no corresponding sorting qualifier yet. But that particular time is not particularly useful. See When was file created
Excellent work on the recognition that not all filenames are nice. This is the robust version I was looking for (since I couldn’t figure out how to use -print0 and printf . Your tip made things quite a bit more elegant than the also-robust BashFAQ/099 (which might get merged with BashFAQ/003).
ls -ABrt1 —group-directories-first | tail -n1
It gives me just the file name, excluding folders.
I like echo *(om[1]) ( zsh syntax) as that just gives the file name and doesn’t invoke any other command.
Works fine if you’re using zsh. Are you not using zsh? I think bash is the default in Ubuntu; you can install zsh, or you can find an equivalent command for bash.
The command is «echo»; it just evaluates its parameters and sends them to standard output. In this case, the glob qualifier «om[1]» has «o», which means order the matching files by «m», which is modified time. And from that ordered list, take [1], which is the first file. You can read about glob qualifiers: zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers
The find / sort solution works great until the number of files gets really large (like an entire file system). Use awk instead to just keep track of the most recent file:
find $DIR -type f -printf "%T@ %p\n" | awk ' BEGIN < recent = 0; file = "" > < if ($1 >recent) < recent = $1; file = $0; >> END < print file; >' | sed 's/^7*\.5* //'
This runs very fast and accurately even on large directories or file systems. Very elegant and efficient solution. Thanks!
The other solutions do not include files that start with ‘.’ .
This command will also include ‘.’ and ‘..’ , which may or may not be what you want:
Will this one return «.» if the directory has been modified more recently than any file contained (say by a deletion)? Maybe that is the desired behavior, maybe not. But you’re right either way.
Shorted variant based on dmckee’s answer:
If you want to get the most recent changed file also including any subdirectories you can do it with this little oneliner:
find . -type f -exec stat -c '%Y %n' <> \; | sort -nr | awk -v var="1" 'NR==1,NR==var ' | while read t f; do d=$(date -d @$t "+%b %d %T %Y"); echo "$d -- $f"; done
If you want to do the same not for changed files, but for accessed files you simple have to change the
%Y parameter from the stat command to %X. And your command for most recent accessed files looks like this:
find . -type f -exec stat -c '%X %n' <> \; | sort -nr | awk -v var="1" 'NR==1,NR==var ' | while read t f; do d=$(date -d @$t "+%b %d %T %Y"); echo "$d -- $f"; done
For both commands you also can change the var=»1″ parameter if you want to list more than just one file.
I personally prefer to use as few not built-in bash commands as I can (to reduce the number of expensive fork and exec syscalls). To sort by date the ls needed to be called. But using of head is not really necessary. I use the following one-liner (works only on systems supporting name pipes):
or to get the name of the oldest file
(Mind the space between the two ‘
If the hidden files are also needed -A arg could be added.
You should explain that the filename is stored in $oldest/newest var. But +1 for least amount of forks.
@squareatom I thought read is fairly well know built-in. But you are right, maybe not. Thanks for your comment!
Assuming you know what a built-in is, you’re prob correct. But, I was just think about the OP’s question. They asked for a command that would return something. Technically your solution doesn’t output anything. So just add «&& echo $newest» or similar to the the end 😉
With only Bash builtins, closely following BashFAQ/003:
shopt -s nullglob for f in * .*; do [[ -d $f ]] && continue [[ $f -nt $latest ]] && latest=$f done printf '%s\n' "$latest"
find $1 -type f -exec stat --format '%Y :%y %n' "<>" \; | sort -nr | cut -d: -f2- | head
using R recursive option .. you may consider this as enhancement for good answers here
Will show the last modified item in the folder. Pair with grep to find latest entries with keywords
If you want file name — last modified, path = /ab/cd/*.log
If you want directory name — last modified, path = /ab/cd/*/
All those ls/tail solutions work perfectly fine for files in a directory — ignoring subdirectories.
In order to include all files in your search (recursively), find can be used. gioele suggested sorting the formatted find output. But be careful with whitespaces (his suggestion doesn’t work with whitespaces).
This should work with all file names:
find $DIR -type f -printf "%T@ %p\n" | sort -n | sed -r 's/^[0-9.]+\s+//' | tail -n 1 | xargs -I<> ls -l "<>"
This sorts by mtime, see man find:
%Ak File's last access time in the format specified by k, which is either `@' or a directive for the C `strftime' function. The possible values for k are listed below; some of them might not be available on all systems, due to differences in `strftime' between systems. @ seconds since Jan. 1, 1970, 00:00 GMT, with fractional part. %Ck File's last status change time in the format specified by k, which is the same as for %A. %Tk File's last modification time in the format specified by k, which is the same as for %A.
So just replace %T with %C to sort by ctime.
Find the files that have been changed in last 24 hours
E.g., a MySQL server is running on my Ubuntu machine. Some data has been changed during the last 24 hours. What (Linux) scripts can find the files that have been changed during the last 24 hours? Please list the file names, file sizes, and modified time.
7 Answers 7
To find all files modified in the last 24 hours (last full day) in a particular specific directory and its sub-directories:
find /directory_path -mtime -1 -ls
The — before 1 is important — it means anything changed one day or less ago. A + before 1 would instead mean anything changed at least one day ago, while having nothing before the 1 would have meant it was changed exacted one day ago, no more, no less.
The argument to -mtime is interpreted as the number of whole days in the age of the file. -mtime +n means strictly greater than, -mtime -n means strictly less than.
Another, more humanist way, is to use -newermt option which understands human-readable time units (see man find and search for -newerXY ).
Unlike -mtime option which requires the user to read find documentation to figure our what time units -mtime expects and then having the user to convert its time units into those, which is error-prone and plain user-unfriendly. -mtime was barely acceptable in 1980s, but in the 21st century -mtime has the convenience and safety of stone age tools.
Example uses of -newermt option with the same duration expressed in different human-friendly units:
find / -newermt "-24 hours" -ls find / -newermt "1 day ago" -ls find / -newermt "yesterday" -ls