I have a folder with three files and basic contents:

$ tail *
==> file1 <==
file 1 contents

==> file2 <==
file 2 contents

==> file3 <==
file 3 contents

I would like to see the contents of the latest file using cat. I tried using it like this:

$ ls -ctr | tail -1

$ ls -ctr | tail -1 | cat

but as you can see, it only prints the name of the last file. I thought the pipe would take the output of tail and process the file with that name, like it does with the subshell command:

$ cat $(ls -ctr | tail -1)
file 3 contents

Why does the redirection method not work, and is there a way to accomplish this with pipes instead of the subshell?

  • Also you should read "Why you shouldn't parse the output of ls". If you're doing it interactively you might be fine, but if you're scripting, please don't do this.
    – Wildcard
    Jan 23, 2016 at 1:01
  • More generally, though, you should clear up the difference between a command's arguments and a command's input.
    – Wildcard
    Jan 23, 2016 at 1:03
  • cat outputs its input, so if the input is file3, the output will be file3; whether it's a file name or anything else. echo file | cat is quite different from cat file. To open the output of a command as file name, use cat $(command).
    – U. Windl
    Oct 8, 2021 at 7:47
  • @U.Windl Regarding cat outputs its input, so if the input is file3, the output will be file3, I'm not sure what you mean. If I type cat file3 I see the contents of the file, not the string file3. Oct 8, 2021 at 10:22
  • "Input" is "stdin" (not to be mixed with "parameter"). You would enter cat, then type file3, and the EOF sequence (^D) to terminate the input.
    – U. Windl
    Oct 8, 2021 at 10:35

3 Answers 3


You want to use the xargs command:

$ ls -ctr | tail -1 | xargs cat

This will take the STDOUT of the tail -1 command, and instead of using it as STDIN for the cat command will use it as options to the cat command.

set   ./file[123]            ### set an arg array of the glob resolution
while [ "${2+:}" ]           ### while there are at least 2 args
do    [ "$1" -nt "$2" ] &&   ### if $1 is newer than $2 then ...
      set "$@" "$1"; shift   ### reset the array to itself + $1; shift regardless
done; cat <"$1"              ### after loop cat $1 or report no glob match
  • 1
    I know you like simple "code only" answers and think they are self-explanatory...but they really could be greatly improved by adding explanations. :) At an absolute minimum if you just briefly said the names of the different features you are using, it would then allow an interested newcomer to at least know what words to put into a search engine to figure out your code.
    – Wildcard
    Jan 23, 2016 at 9:16
  • 3
    @Wildcard - i welcome edits and would thank you for any you cared to contribute. but, as usual, you have a point...
    – mikeserv
    Jan 23, 2016 at 9:36
  • 1
    Accepted because of the clear explanation of the code given. Jan 23, 2016 at 17:12
  • @user1717828 - please look again at the edit - this fixes the subtle bug of the previous snippet's tendency to forever loop when ${1}t == ${2}t. This version will always prefer the last of two such in the lexical sort order.
    – mikeserv
    Jan 23, 2016 at 17:34

You could try something like

less $(ls -ctr | tail - 1)

(the $(...) isn't universal, you might need to substitute backticks). Change less to taste.

  • > with pipes instead of the subshell? Jan 23, 2016 at 0:47
  • @user1717828, a subshell is definitely cheaper that launching xargs
    – vonbrand
    Jan 23, 2016 at 0:48

You must log in to answer this question.

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