8

The following commands work

$ ls -1t | head -1
git_sync_log20180924_00.txt

$ vi git_sync_log20180924_00.txt

But this does not

$ ls -1t | head -1 | vi
Vim: Warning: Input is not from a terminal
Vim: Error reading input, exiting...
Vim: preserving files...
Vim: Finished.

How can I accomplish this (open most recently modified file in vi)?

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250

3 Answers3

17
vi -- "$(ls -t | head -n 1)"

(that assumes file names don't contain newline characters).

Or if using zsh:

vi ./*(om[1])

Or:

vi ./*(.om[1])

To only consider regular files.

vi ./*(.Dom[1])

To also consider hidden files (as if using ls -At).

Those work regardless of what characters or byte values the file names may contain. For a GNU shell&utilities equivalent of that latter one, you can do:

IFS= read -rd '' file < <(
  find . ! -name . -prune -type f -printf '%T@\t%p\0' | sort -zrn | cut -zf2-) &&
  vi "$file"
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
7

With your pipeline structure, you could use xargs like so:

ls -1t | head -1 | xargs vi
Ketan Maheshwari
  • 9,054
  • 6
  • 40
  • 53
  • 2
    That assumes file names don't contain blanks, newline, single quote, double quotes, backslashes and also means that `vi`'s stdin will be either the pipe from `head` or `/dev/null` depending on the `xargs` implementation instead of the tty. – Stéphane Chazelas Sep 24 '18 at 14:05
  • 1
    make that `... | xargs sh -c 'vi –  Sep 24 '18 at 14:08
  • @mosvy, or with GNU `xargs` and a shell with process substitution: `xargs -r0d '\n' -a <(ls -t | head -n 1) vi --` – Stéphane Chazelas Sep 24 '18 at 14:10
2

If you need a more solid solution that doesn’t rely on the flaky output of ls you can resort to stat(1). Most implementations have some way to specify a custom output format which can include timestamps to feed to sort(1) or an Awk script. Some examples:

  • with GNU coreutils:

    stat -L -c '%Y %n' -- *
    
  • with BSD coreutils:

    stat -L -t '%s' -f '%Sm %N' ./*
    

Subsequently you can sort and filter the result:

  • with coreutils only:

    stat ... | sort -t ' ' -k 1,1 -n -r | head -n 1 | cut -d ' ' -f 2-
    

    This requires O(n log n) time to sort the entire input even though you only need the maximum.

  • with Awk:

    stat ... | awk -F ' ' 'NR == 1 || $1 > m { m = $1; n = substr($0, length(m) + 2); } END{ if (NR) print(n); }'
    

    This runs in O(n) time because it only compares and updates the maximum.

David Foerster
  • 1,505
  • 1
  • 11
  • 18
  • How is that less _flaky_ than using `ls`? That still assumes file names don't contain newline characters. You could make it work with the `stat` builtin of `zsh` (which predates both GNU and BSD `stat`) but then `zsh` has builtin support for for sorting file names by modification time, so you don't need `stat` at all there. Or by using `--printf '%Y %n\0'` with GNU `stat` and GNU `sort -zrn`. Also note that the GNU `stat` variant doesn't work for a file called `-`. – Stéphane Chazelas Sep 24 '18 at 19:21