8

I have a directory of .jpg files that continuously grows. I want to copy the most recent one elsewhere. This is what I currently have and it works, just curious if there's a better way to identify the most recent jpg file rather than using ls and tail.

#!/bin/bash
cd /home/pi/JPGS
fn=$(ls -rt1 | tail -1)
mv -f $fn /home/pi/WWW/webpic.jpg
Levon
  • 11,384
  • 4
  • 45
  • 41

4 Answers4

14

There's a number of characters in file names that would make that fail. You can improve it with:

#! /bin/sh -
cd /home/pi/JPGS || exit
fn=$(ls -t | head -n1)
mv -f -- "$fn" /home/pi/WWW/webpic.jpg

Leaving a variable unquoted in list context (in Bourne-like shells other than zsh) is the split+glob operator, you almost never want to do that. -- marks the end of the options so "$fn" will not be taken as an option if it starts with -.

That still fails if filenames contain newline characters, but not space, tab, star, question mark, right square bracket, or start with dash.

Best is to use zsh here:

#! /bin/zsh -
mv -f /home/pi/JPGS/*.jpg(.om[1]) /home/pi/WWW/webpic.jpg

(.om[1]) are glob qualifiers, they are a zsh specific feature. . restricts the glob to regular files (will not include symlinks, directories, devices...), om is to order on modification time, and [1] to only take the first file.

Note that if you want to assign that to a shell variable, that would have to be an array variable:

fn=(/home/pi/JPGS/*.jpg(.om[1]))

(not that it makes a lot of difference on how you use it later).

  • Thanks - I have a few questions: What's the significance of the -- and why is zsh preferable over bash here? I'm also not sure (.om[1]) does. – Levon Dec 21 '13 at 21:57
  • 1
    @Levon, answer updated with details. – Stéphane Chazelas Dec 21 '13 at 22:21
  • Thanks .. good to store this away for future possible use, my filenames are quite regular so I won't have to worry about special cases for this. – Levon Dec 22 '13 at 00:24
  • 1
    @Levon, that's no reason (IMO) to use the wrong syntax. You should really get used to not writing things like mv $f xx which is the wrong syntax and bad practice (and where most of the shell-related vulnerabilities come from) but mv -- "$f" xx – Stéphane Chazelas Dec 22 '13 at 13:58
  • You are right of course, my comment was with regard to the globing, not re -- which is clearly important from a security standpoint too - thanks again – Levon Dec 22 '13 at 14:13
  • instead of "ls -t" you could perhaps use "find . -maxdepth 1 -type f | head -n1" to limit to regular files... – hajikelist Feb 01 '18 at 23:52
  • @hajikelist, that would not sort the list by modification time. – Stéphane Chazelas Jun 22 '20 at 16:08
8

Listing file

You could reverse the logic on the ls a bit.

$ ls -t | head -n1

Details

   -t     sort by modification time, newest first

Now it shows up first so we can use head to return the first result.

NOTE: You could also sort the list by change time (ctime), though you're probably going to want to use modify time above - (mtime). The ctime is the last time the file status meta information was changed.

   -c     with -lt: sort by, and show, ctime (time of last modification of 
          file status information) with -l: show ctime and sort by name
          otherwise: sort by ctime, newest first

For example:

$ ls -tc | head -n1

Moving the file

To do the move more cleanly you'll want to wrap the filename in double quotes.

Example

$ mv -f -- "$fn" /home/pi/WWW/webpic.jpg

This will work in the majority of cases, there are a handful of legal filenames where it won't, for example, files with new lines. But these, though legal, are rarely ever intentionally used.

slm
  • 369,824
  • Thanks - I do know the filenames, so this won't be an issue. What is the advantage of using double-quotes around the variable? – Levon Dec 21 '13 at 21:58
  • he'll probably want to sort on mtime (which reflects the modification time of the content of the file) rather than ctime (which reflects the modification time of metadata as well). – Stéphane Chazelas Dec 21 '13 at 22:05
  • Note that, portably (and as the documentation you quote mentions), you need the -t option as well: ls -tc to sort on the inode-change time (and ls -lc to display the inode-change time, and ls -ut to sort on access time) – Stéphane Chazelas Dec 21 '13 at 22:20
  • 1
    @Levon - double quotes protects the mv command in cases where there are spaces within the file names. – slm Dec 21 '13 at 22:57
  • 1
    Thanks, very useful info re the use of "" .. my current filenames won't have that problem, but I will start using this as the default way of doing things. – Levon Dec 22 '13 at 00:25
0

To assign the most recent file with a jpg suffix, wherever it is, to a shell variable:

fn=$( locate .jpg | xargs ls -rt | tail -1)

Thanks to locate, this is quick even if you have a large number of files. Main drawback: files modified since the last update of the locate database are not taken into account.

Another drawback: does not behave well when there is no file with .jpg (because of ls).

0

I guess I'd use find as it is more general. I just ran into a variant of this, determining the newest executable but it comes up in other places. The find command,

find .. -type f -printf "%T@ %p\n" | sort -g

should be quite flexible and work with any file names. Change the path and criteria as you need and then tail the output.

Although I admit for my case I ended up with,

EXIF=`ls -t \`which -a exiftool \` | awk '{print $1}' `

which -a setup /home/scripts/setup /home/scripts/lat/scripts/setup /home/documents/latex/scripts/setup marchywka@happy$ ls -t which -a setup /home/scripts/setup /home/documents/latex/scripts/setup /home/scripts/lat/scripts/setup marchywka@happy$ ls -alt ls -t \which -a setup ` ` -rwxrwxrwx 1 root root 897 Oct 8 17:54 /home/scripts/setup -rwxrwxrwx 1 root root 166 Sep 13 13:50 /home/scripts/lat/scripts/setup -rwxrwxrwx 1 root root 163 Jan 27 2019 /home/documents/latex/scripts/setup