Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

So I know parsing ls is bad!.

Today I found this nugget:

FILENAME=`ls -t $READ_FOLDER | head -1`

So I believe originally this was designed to grab the newest filename, I actually now want to adapt it, to grab maybe the 5th newest file as the process that's checking it is a little slow, and so we can't be sure they have been processed yet.

I'm working in bash, but not opposed to a sh generic solution too.

share|improve this question

5 Answers 5

up vote 3 down vote accepted

You can use GNU find to list the files with the modified time expressed as epoch time, then use sort to sort the list, finally head and tail to get the desired numbered file name :

find . -maxdepth 1 -type f -printf '%T@ %p\n' | sort -k1,1nr | head -5 | tail -1
share|improve this answer
    
Note that this doesn't completely avoid problems caused by filenames containing newlines... –  don_crissti yesterday
4  
Workaround for unusual filenames: find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -z -k1,1nr | awk -v 'RS=\0' 'NR==5' –  Roman yesterday

Perlishly:

#!/usr/bin/env perl

use strict;
use warnings;

my %files_by_mtime;

foreach my $file ( glob ( "./*" ) ) {
   my ( $mtime ) = (stat($file))[9];
   $files_by_mtime{$file} = $mtime; 
}

foreach my $f (  sort { $files_by_mtime{$b} <=> $files_by_mtime{$a} } keys %files_by_mtime ) {
   print "$f $files_by_mtime{$f}\n";
}

Reducing to:

perl -e '%f = map { $_ => (stat)[9] } glob("./*");print "" . ( sort { $f{$b} <=> $f{$a} } keys %f )[4];'

Where the "N"th file is 4, because perl arrays start from zero.

share|improve this answer

Assuming you have access to GNU tools (you do if you're running Linux), I would use stat instead. For example:

$ ls -l
total 0
-rw-r--r-- 1 terdon terdon 0 Sep 15 16:49 file1
-rw-r--r-- 1 terdon terdon 0 Sep 15 16:39 file2
-rw-r--r-- 1 terdon terdon 0 Sep 15 16:29 file3
-rw-r--r-- 1 terdon terdon 0 Sep 15 16:19 file4
-rw-r--r-- 1 terdon terdon 0 Sep 15 16:09 file5
-rw-r--r-- 1 terdon terdon 0 Sep 15 15:59 file6
-rw-r--r-- 1 terdon terdon 0 Sep 15 15:49 file7

So, the 5th newest file is file5. To print just that, you can do:

$ stat --printf '%Y %n\0' * | sort -zrnk1 | 
    awk -vRS='\0' 'NR==5{sub(/^[^ ]* /,"",$0); print}'

You could then easily make this into a shell function that can take N (5 in your example) as an argument. Just add these lines to your ~/.bashrc or equivalent:

nthfile() {
  stat --printf '%Y %n\0' * | sort -zrnk1 |
     awk -vRS='\0' -vn="$1" 'NR==n{sub(/^[^ ]* /,"",$0); print}'
}

Note that this will also show you directories. If you need it to match hidden files as well run (assuming you're using bash) shopt -s dotglob before the above command.

Explanation

  • stat --printf '%Y %n\0' * : for each file or directory in the current folder, print the modification date in seconds since the epoch (%s) and the file name (%n) and end each line with \0 instead of \n. This lets us deal correctly with file names containing newline characters.
  • sort -zrnk1 : sort the output in reverse sort order (-r), from the newest to the eldest. The -z tells sort to expect null-terminated input lines. The -n tells it to sort numerically and the -k1 to only consider the first field when sorting.
  • awk:
    • -vRS='\0' : set the input record (line) separator to \0;
    • vn=$1 : set the variable n to whatever was given as input to the function;
    • NR==n{} : run this only on line n (5 in the first example);
    • sub(/^[^ ]* /,"",$0); print : substitute all non-space characters from the beginning of the line (^[^ ]*) up to the 1st space and print the result
share|improve this answer
1  
Don't you need to tell sort to sort numerically (sort -zrnk1 or something like that)? –  zwol 23 hours ago
    
Shell pattern * does not match filenames starting with .. –  Roman 21 hours ago
    
@zwol it shouldn't be necessary, no. The output of stat will always be seconds since the epoch so sort should always deal with it correctly. –  terdon 20 hours ago
    
@Roman true but neither does ls -t which is what the OP was using. There was no mention of dotfiles in the question. Still, I edited to add the dotglob option for bash users. –  terdon 20 hours ago
1  
Thanks for dotglob. About sort without -n: I believe, it will correctly sort numbers, only if they all have the same length (same amount of digits). –  Roman 20 hours ago

One way to do this (assuming no newlines in your file names) would be

ls -t ... | head -n5 | tail -n1
share|improve this answer
3  
Per the title, I think OP wants to do it without parsing ls... –  don_crissti yesterday
1  
@MatthewRock - sure but some people here might argue that it won't work properly if there are filenames containing newlines; anyway, assuming the OP wanted to use ls and there were no such filenames, the question would be a duplicate of How do I display the nth result of an ls command? –  don_crissti yesterday
    
@don_crissti: this is not parsing (the output) of ls, it's just using it. –  Thomas Erker yesterday
    
@don_crissti I overlooked the link given by the OP. The problem is not ls, but the processing in the pipe which will break at filenames with embedded newlines etc. –  Thomas Erker yesterday
1  
@ThomasErker this is reading the output of ls and modifying its output depending on certain criteria. Otherwise known as parsing it. Like most cases of parsing ls output, it will fail on file names with newlines. –  terdon yesterday

With zsh:

print -r -- *(.Dom[5])

give you the 5th file in current directory sorted by modification time. Change m to a to sort by access time. . and D include only regular and hidden files.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.