I need to write a script called newer.sh which given a list of files, returns the newest one. That is, typing:

newer.sh /some/file other-file /and/so/on

will return the name of the newest file in the list.

This is what I have been able to come up with till now:

echo "Newest file:"
ls -t | head -n 1

It does actually return the latest file created on the basis of its time of creation, but only among the files in the current directory.

  • 2
    show your efforts. Also, Given a list of filenames - is it a static list or they are all in the same folder? – RomanPerekhrest May 4 '17 at 7:05
  • 4
    I'm voting to close this question as off-topic because it shows zero effort and is obviously homework – jasonwryan May 4 '17 at 7:06
  • @RomanPerekhrest I am new to shell, bash and linux and have no idea how to go about it. I have actually been able to get some result, but from a very basic command: echo "Newer List" ls -t | head -n1. It does return the latest file, after testing it 3 times. But, I am not sure if my command is correct. – Shivang Chaturvedi May 4 '17 at 7:28
  • @jasonwryan well, I will not stop you sir. But for someone who has no idea about this stuff, I thought people here would be kinder than this. – Shivang Chaturvedi May 4 '17 at 7:30
  • @StéphaneChazelas all I have done is copied the question. You can imagine how difficult it is for me to interpret it, and write a code on it as well. – Shivang Chaturvedi May 4 '17 at 7:31

With GNU tools:

#! /bin/bash -
export LC_ALL=C
eval "files=($(ls -dt --quoting-style=shell-always -- "${@?}"))"
((${#files[@]} > 0)) && printf '%s\n' "${files[0]}"

For symlinks, that would consider the last-modification time of the symlink itself. If you'd rather it be that of the target of the symlink, add the -L option to ls.

If you want to add the restriction that no file paths given as argument may contain newline characters, you can write:

#! /bin/sh -
# only works for file paths that don't contain newline characters
[ "$#" -gt 0 ] && ls -td -- "$@" | head -n 1

and remove the dependency on GNU tools.

The principle is the same: use ls -t's ability to sort files by modification time and get the first in that list. But here, ls outputs the list of files one on each line which makes it impossible to parse reliably unless you can guarantee that none of the file paths will contain newline characters (otherwise, you wouldn't be able to tell the difference between a file called a<newline>b and two a and b files and if a<newline>b was the newest file, head -n 1 would only display the a giving in effect the wrong result).

Compared to your approach, here, we're passing the list of arguments that the script received ("$@") to ls (after a -- so that those arguments are treated by ls as file operands and not options) and passing the -d option so that ls doesn't list the content of those files that are of type directory. We're also not doing it if the script received 0 arguments ($# == 0) as otherwise, ls -td -- would list the current directory, so you'd get . as output.

Instead of GNU ls, you could also use GNU stat to retrieve the file's modification time in a way that can be sorted with GNU sort:

#! /bin/sh -
export TZ=UTC0 LC_ALL=C
stat --printf '%y@%n\0' -- "$@" | sort -rz | sed -z 's/^[^@]*@//;q' | tr '\0' '\n'

(with a recent version of GNU sed for -z). Or using GNU awk:

#! /bin/sh -
export TZ=UTC0 LC_ALL=C
stat --printf '%y@%n\0' -- "$@" | awk -v RS='\0' '
  NR == 1 || $0 > newest {newest = $0}
  END {if (NR) {sub(/[^@]*@/, "", newest); print newest}}'
  • 1
    While I don't believe that this is what a beginner needs to do his homework, I suggest to add -q to the ls options in the second script to make it work with newlines. – Philippos May 4 '17 at 8:05
  • @Philippos, depends what we mean by work. Returning a?b for a file called a\nb or a\tb instead of a (or a<tab>b) may be more useful when the result is intended for display-only to a user, but it would do more harm than help if it's meant to be post-processed, like ls -ld -- "$(newest.sh *.txt)" – Stéphane Chazelas May 4 '17 at 8:21
  • In this case use the -b option instead (GNU ls). You can use echo -e to revert the escaping for further processing: /bin/echo -e $(ls -tdb -- "$@" | head -n 1) – Philippos May 4 '17 at 9:04
  • 2
    @Philippos, no, that doesn't work. For instance, ls -b 'a b' outputs a\ b and even assuming /bin/echo is GNU echo and POSIXLY_CORRECT is not in the environment, echo -e 'a\ b' outputs a\ b, not a b (not to mention the problems with blanks and wildcards because of the missing quotes, or problems with files called -e or -n, -nen...). Also ls -b $'\x80' outputs \200 when echo expects \0200. – Stéphane Chazelas May 4 '17 at 9:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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