311

Looking for a command that will return the single most recent file in a directory.

Not seeing a limit parameter to ls...

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
ack
  • 14,285
  • 22
  • 55
  • 73
  • 4
    `watch -n1 'ls -Art | tail -n 1'` - shows the very last files –  Jul 05 '12 at 19:52
  • 5
    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](https://mywiki.wooledge.org/BashFAQ/099) which gives a POSIX answer to this problem – kvantour Jul 08 '20 at 15:19
  • Also very useful: https://unix.stackexchange.com/questions/29899/how-can-i-use-find-and-sort-the-results-by-mtime – Basj Sep 14 '21 at 07:02
  • newer but similar question is here: [Bash script to find and display oldest file](https://stackoverflow.com/questions/27097167/bash-script-to-find-and-display-oldest-file/64156260) – meolic Dec 16 '21 at 09:47
  • @kvantour , I had the same concern. However, [the answer](https://stackoverflow.com/a/38996701/6505499) 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](https://mywiki.wooledge.org/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.) – bballdave025 Sep 16 '22 at 23:16

23 Answers23

442
ls -Art | tail -n 1

This will return the latest modified file or directory. Not very elegant, but it works.

Used flags:

-A list all files except . and ..

-r reverse order while sorting

-t sort by time, newest first

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
  • 11
    using ls -Artls you can also view the file date. – Josir Sep 18 '14 at 14:37
  • 28
    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. – TrueY Nov 14 '14 at 08:51
  • 4
    @TrueY I quite agree. Chaos' answer is better for efficiency. – dmckee --- ex-moderator kitten Nov 14 '14 at 16:01
  • 3
    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". – Sildoreth Mar 13 '15 at 18:06
  • 1
    @dmckee you should either remove -r from command or change tail to head :D – 89n3ur0n May 15 '15 at 12:01
  • 1
    For those who just want the filename: $(ls -rt | tail -n 1) – Alan Jun 16 '15 at 15:23
  • 4
    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. – Peter Scott Feb 05 '18 at 00:21
  • Works on Mac :) – igo Jun 03 '20 at 19:00
  • This lists the most recent file of a folder not the most recent file of a directory. – 12431234123412341234123 Dec 30 '21 at 13:31
  • Thanks for the answer! How would one siphon this into another command? For example, how could you use `rm -rf` to remove the latest file spat out by the command you give? – Gregor Hartl Watters Apr 05 '22 at 11:21
  • how to open the file in addition to show its name? Thanks! – Adel Sep 26 '22 at 10:06
262
ls -t | head -n1

This command actually gives the latest modified file or directory in the current working directory.

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
chaos
  • 122,029
  • 33
  • 303
  • 309
169

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
  • | is a shell pipe (see man bash section on Pipelines)
  • sort -n means to sort on the first column and to treat the token as numerical instead of lexicographic (see man sort)
  • cut -d' ' -f 2- means to split each line using the character and then to print all tokens starting at the second token (see man cut)
    • NOTE: -f 2 would print only the second token
  • tail -n 1 means to print the last line (see man tail)
Trevor Boyd Smith
  • 18,164
  • 32
  • 127
  • 177
gioele
  • 9,748
  • 5
  • 55
  • 80
  • 8
    Here's a solution for Mac: `find $DIR -type f -exec stat -lt "%Y-%m-%d" {} \+ | cut -d' ' -f6- | sort -n | tail -1` – user Oct 12 '15 at 20:32
  • 2
    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 – Kevin Jan 11 '17 at 15:02
  • 3
    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.) – Dennis Estenson May 14 '17 at 11:48
  • 3
    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` – Simon Jul 27 '17 at 08:33
  • 3
    @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` – yoz Dec 11 '19 at 21:21
  • 1
    IMO this is the most useful of all the answers here. Thank you! – balu Jun 10 '21 at 17:42
  • The `sort`, `cut`, and `tail` combination can be replaced with a single `awk` looking for the maximum number -- then outputting the second field at the end. An O(n) operation rather than O(n*ln(n)), that `sort` requires: `awk '$1 > latest { latest = $1; p = $2 } END { print p }'` – Mikhail T. Sep 23 '22 at 14:56
  • it probably goes without saying, but imho `find` is in many ways (for this specific question) preferable to `ls`, and can be used instead of `ls` in many of the other answers provided. But in this case, one would prefer *not* to have a recursive file listing. So to just list files in the current directory, add in `find . -maxdepth 1 -type f ....` etc etc – michael Nov 12 '22 at 06:27
29

A note about reliability:

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))"
((${#files[@]} > 0)) && printf '%s\n' "${files[0]}"

(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' "${files[0]}"

Or recursively:

readarray -t -d '' files < <(
  LC_ALL=C find . -name . -o -name '.*' -prune -o -type f -printf '%T@%p\0' |
  sort -rzn | cut -zd/ -f2-)

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:

printf '%s\n' *(.om[1])

Including hidden ones:

printf '%s\n' *(D.om[1])

Second newest:

printf '%s\n' *(.om[2])

Check file age after symlink resolution:

printf '%s\n' *(-.om[1])

Recursively:

printf '%s\n' **/*(.om[1])

Also, with the completion system (compinit and co) enabled, Ctrl+Xm becomes a completer that expands to the newest file.

So:

vi Ctrl+Xm

Would make you edit the newest file (you also get a chance to see which it before you press Return).

vi Alt+2Ctrl+Xm

For the second-newest file.

vi *.cCtrl+Xm

for the newest c file.

vi *(.)Ctrl+Xm

for the newest regular file (not directory, nor fifo/device...), and so on.

Stephane Chazelas
  • 5,859
  • 2
  • 34
  • 31
  • Thanks for `zsh` tips. Could you provide a link to more details about this in zsh docs ? – freezed Apr 18 '20 at 09:31
  • @freezed, see `info zsh qualifiers` – Stephane Chazelas Apr 18 '20 at 20:03
  • 1
    Thanks @Stephane Chazelas – freezed Apr 20 '20 at 07:41
  • 1
    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 https://unix.stackexchange.com/questions/552103/limiting-the-scope-of-glob-qualifiers-in-zsh on how to accomplish it over multiple folders. – Martin von Wittich Sep 07 '20 at 20:43
  • I always want to see the newest created file instead os newest modified file. so: `printf '%s\n' **/*(.oc[1])` – SergioAraujo Nov 07 '20 at 00:05
  • 2
    @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](//unix.stackexchange.com/a/119699) – Stephane Chazelas Nov 07 '20 at 06:32
  • 1
    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](https://mywiki.wooledge.org/BashFAQ/099) (which might get merged with [BashFAQ/003](https://mywiki.wooledge.org/BashFAQ/003)). – bballdave025 Sep 16 '22 at 23:09
  • The `bash` answer is great, too. – bballdave025 Sep 16 '22 at 23:21
  • is the `! -name '.*'` in your bash 4.4 solution negating the name pattern...ah.. it's saying "ignore the dot files". – Jason Harrison Apr 01 '23 at 00:46
11

I use:

ls -ABrt1 --group-directories-first | tail -n1

It gives me just the file name, excluding folders.

user1195354
  • 111
  • 1
  • 2
10

I like echo *(om[1]) (zsh syntax) as that just gives the file name and doesn't invoke any other command.

Stephane Chazelas
  • 5,859
  • 2
  • 34
  • 31
MikeB
  • 1,452
  • 14
  • 28
  • 1
    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. – MikeB Sep 22 '14 at 00:13
  • 3
    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: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers – MikeB Sep 22 '14 at 00:14
9

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/^[0-9]*\.[0-9]* //'
Patrick
  • 91
  • 1
  • 2
  • This runs very fast and accurately even on large directories or file systems. Very elegant and efficient solution. Thanks! – cecilkorik Dec 18 '20 at 06:57
8

ls -lAtr | tail -1

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:

ls -latr | tail -1

Jared Oberhaus
  • 14,547
  • 4
  • 56
  • 55
  • 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. – dmckee --- ex-moderator kitten Jun 18 '09 at 23:25
7

Shorted variant based on dmckee's answer:

ls -t | head -1
  • 1
    but the `-1` syntax to head has been deprecated quite some time ago, and `-n 1` should be used. – tink Jan 07 '20 at 19:34
6

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 {print $0}' | 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 {print $0}' | 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.

OASE Software GmbH
  • 152
  • 1
  • 3
  • 11
5

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):

read newest < <(ls -t *.log)

or to get the name of the oldest file

read oldest < <(ls -rt *.log)

(Mind the space between the two '<' marks!)

If the hidden files are also needed -A arg could be added.

I hope this could help.

TrueY
  • 7,360
  • 1
  • 41
  • 46
  • You should explain that the filename is stored in $oldest/newest var. But +1 for least amount of forks. – squareatom Mar 02 '15 at 15:28
  • @squareatom I thought read is fairly well know built-in. But you are right, maybe not. Thanks for your comment! – TrueY Mar 02 '15 at 22:01
  • 4
    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 ;-) – squareatom Mar 08 '15 at 20:52
5

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"
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
3

Recursively:

find $1 -type f -exec stat --format '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head
Konki
  • 186
  • 1
  • 4
3

using R recursive option .. you may consider this as enhancement for good answers here

ls -arRtlh | tail -50
mebada
  • 2,252
  • 4
  • 27
  • 35
2

try this simple command

ls -ltq  <path>  | head -n 1

If you want file name - last modified, path = /ab/cd/*.log

If you want directory name - last modified, path = /ab/cd/*/

fedorqui
  • 275,237
  • 103
  • 548
  • 598
2
ls -t -1 | sed '1q'

Will show the last modified item in the folder. Pair with grep to find latest entries with keywords

ls -t -1 | grep foo | sed '1q'
fedorqui
  • 275,237
  • 103
  • 548
  • 598
1

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.

basic6
  • 3,643
  • 3
  • 42
  • 47
  • You are correct about the white space problem with @gioele suggestion, but you only need to add a hyphen to the -f option to make it a range to fix that: `find $DIR -type f -printf "%T@ %p\n" | sort -n | cut -d' ' -f 2- | tail -n 1` Or alternatively, keep all except the first field: `find $DIR -type f -printf "%T@ %p\n" | sort -n | cut -d' ' -f 1 --complement | tail -n 1` – Aaron Jun 29 '15 at 16:53
1

Finding the most current file in every directory according to a pattern, e.g. the sub directories of the working directory that have name ending with "tmp" (case insensitive):

find . -iname \*tmp -type d -exec sh -c "ls -lArt {} | tail -n 1" \;
xb.
  • 1,617
  • 11
  • 16
1

Presuming you don't care about hidden files that start with a .

ls -rt | tail -n 1

Otherwise

ls -Art | tail -n 1
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
jasonleonhard
  • 12,047
  • 89
  • 66
1
ls -Frt | grep "[^/]$" | tail -n 1
  • 2
    There are other answers that provide the OP's question, and they were posted many years ago. When posting an answer, please make sure you add either a new solution, or a substantially better explanation, especially when answering older questions. – help-info.de Apr 15 '19 at 17:23
  • 3
    @help-info.de Please don't vote to delete answers that are code-only. They may not be great, but [deletion is for things that are not answers](https://meta.stackoverflow.com/q/287563) – Machavity Apr 15 '19 at 18:32
  • @Machavity - I hope not to have failed and did a comment only for late answer. – help-info.de Apr 15 '19 at 19:07
1

If you want to find the last modified folder name within /apps/test directory then you can put below code snippet in a batch script and execute it which will print the name of the last modified folder name.

#!/bin/bash -e

export latestModifiedFolderName=$(ls -td /apps/test/*/ | head -n1)

echo Latest modified folder within /apps/test directory is $latestModifiedFolderName
0

Find the file with prefix of shravan* and view

less $(ls -Art shravan* | tail -n 1)
-1

I needed to do it too, and I found these commands. these work for me:

If you want last file by its date of creation in folder(access time) :

ls -Aru | tail -n 1  

And if you want last file that has changes in its content (modify time) :

ls -Art | tail -n 1  
sampathsris
  • 21,564
  • 12
  • 71
  • 98