7

I frequently download PDF files with heinous numeric file names from my browser. These automatically go into ~/Downloads. Ideally I would like to just be able to open these files with:

evince "the most recently modified file"

without having to open ~/Downloads to find the file name.

Is there a simple way to specify "the most recently modified file" in bash?

NOTE: I know that it is possible to do this, but ideally I am looking for a solution that would be simpler than ls -t'ing ~/Downloads to check the name.

1
  • 1
    There is no way to do this with bash that is both simple and foolproof. I believe zsh can do this with the *(om[1]) glob.
    – jw013
    Mar 22, 2012 at 15:11

5 Answers 5

10
evince "$(ls -t | head -n1)"

While it will handle spaces, tabs, and (I believe) printing specials, it will break if the filename contains a newline, and possibly on some other non-printing characters.

5
  • This won't work for a lot of different files.
    – l0b0
    Mar 22, 2012 at 15:14
  • 1
    @l0b0 I am aware of the limitations of parsing ls, that's why I mentioned them.
    – Kevin
    Mar 22, 2012 at 15:24
  • thanks, this is simple and will definitely work for my purposes (who the heck puts newlines in a filename!?)
    – DQdlM
    Apr 4, 2012 at 14:05
  • @DQdlM: Hackers and people who test their code properly.
    – l0b0
    Apr 4, 2012 at 14:21
  • This is good enough for most simple cases. Feb 28, 2019 at 15:13
5

This is a more correct and robust approach than ls -t, at the cost of some additional complexity.

Setup

Add a short shell script (code below) to your $PATH. ~/bin is a good place for it.

Remember to make sure

  • the script is executable chmod +x ~/bin/script_name
  • ~/bin is in your $PATH

Usage

Pass the command you want to run on the newest file in ~/Download to last_download.

Examples

Assuming you named the script last_download

  • last_download (no arguments): runs evince, the default command, on the newest file in ~/Downloads
  • last_download mplayer: runs mplayer on the newest file in ~/Downloads
  • last_download cp -t ~/Desktop: copies the newest file in ~/Downloads to ~/Desktop

Code

#!/bin/sh
# Usage: last_download [cmd [options]...]

newest=
dir=~/Downloads

# default command
if [ $# -eq 0 ]; then
        set -- evince;
fi

# find newest file
for f in "$dir"/*; do
        if [ -z "$newest" ] || [ "$f" -nt "$newest" ]; then
                newest="$f"
        fi
done
if ! [ -e "$newest" ]; then
        exit 1
fi

# run command on newest file
"$@" "$newest"

Note: The script only looks in ~/Download but it would not be hard to generalize it to support any directory, in which case a name change would also be warranted.

15
  • Well done, nice and thorough.
    – Kevin
    Mar 22, 2012 at 16:01
  • 1
    ... except it puts the command to run outside your shell context, so it won't work with bash builtin commands, aliases, functions, anything set up in your bashrc, etc.
    – peth
    Mar 22, 2012 at 16:48
  • @user112553 Yes that is a good point. However, it would be trivial to convert the script into a function if the user so desired.
    – jw013
    Mar 22, 2012 at 17:32
  • True. I just saw everyone else nitpicking and wanted my own nit. ;)
    – peth
    Mar 22, 2012 at 17:56
  • @jw013 Sorry to ask (I'm a novice) but, how would you convert the script into a function so that it will recognize your aliases, etc? I tried: function last_file { source ~/scripts/last_file.sh } At the end of my .bashrc, but aliases were not preserved.
    – user1247
    Mar 31, 2012 at 12:49
4
exec zsh
evince ~/Downloads/*.pdf(om[1])

or if you don't want to switch to zsh

cd ~/Downloads
zsh -c 'evince ./*.pdf(om[1])'

The bits between parentheses are glob qualifiers. om changes the order on globs to use the modification time rather than the name (newest first). [1] means to use only the first match.

For more information, see this answer to "How do I filter a glob in zsh".

3

You can use a modified find_date_sorted:

last_download() {
    while IFS= read -r -d '' -u 9
    do
        cut -d ' ' -f 3- <<< "$REPLY"
        break
    done 9< <(find ~/Downloads -maxdepth 1 -type f -printf '%TY-%Tm-%Td %TH:%TM:%TS %p\0' | sort -rz)
}

It'll work with any filenames. To open the file:

evince "$(last_download)"
5
  • 1
    Actually this doesn't work for files with a newline in their name.
    – Kevin
    Mar 22, 2012 at 15:29
  • @Kevin: Ah yes, because of head. My bad. Should be fixed now.
    – l0b0
    Mar 22, 2012 at 16:09
  • Even though file names ending in newlines are uncommon, it should be noted that command substitution strips trailing newlines, even when quoted.
    – jw013
    Mar 23, 2012 at 14:32
  • D'oh! You're right, of course. You'd have to use a trick to fix that.
    – l0b0
    Mar 23, 2012 at 14:39
  • Thanks for this answer, I always learn way more than I intend when I come on SE!
    – DQdlM
    Apr 4, 2012 at 14:07
0

No, and even if there were, you couldn't be sure some system log won't usually be the most recently updated file.

Kevin's answer (he beat me to it) works if you just want to avoid manually opening ~/Downloads.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .