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.

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.

share|improve this question
    
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 '12 at 15:11
add comment

5 Answers

up vote 4 down vote accepted
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.

share|improve this answer
    
This won't work for a lot of different files. –  l0b0 Mar 22 '12 at 15:14
1  
@l0b0 I am aware of the limitations of parsing ls, that's why I mentioned them. –  Kevin Mar 22 '12 at 15:24
    
thanks, this is simple and will definitely work for my purposes (who the heck puts newlines in a filename!?) –  KennyPeanuts Apr 4 '12 at 14:05
    
@DQdlM: Hackers and people who test their code properly. –  l0b0 Apr 4 '12 at 14:21
add comment
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 How do I filter a glob in zsh

share|improve this answer
add comment

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.

share|improve this answer
    
Well done, nice and thorough. –  Kevin Mar 22 '12 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. –  user112553 Mar 22 '12 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 '12 at 17:32
    
True. I just saw everyone else nitpicking and wanted my own nit. ;) –  user112553 Mar 22 '12 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 '12 at 12:49
show 10 more comments

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)"
share|improve this answer
1  
Actually this doesn't work for files with a newline in their name. –  Kevin Mar 22 '12 at 15:29
    
@Kevin: Ah yes, because of head. My bad. Should be fixed now. –  l0b0 Mar 22 '12 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 '12 at 14:32
    
D'oh! You're right, of course. You'd have to use a trick to fix that. –  l0b0 Mar 23 '12 at 14:39
    
Thanks for this answer, I always learn way more than I intend when I come on SE! –  KennyPeanuts Apr 4 '12 at 14:07
add comment

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.

share|improve this answer
add comment

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.