0

I am trying to pass output of one command to other:

ls -lt *.txt | tail -n 3 | cat

I want to list all .txt files, take last 2, and display their contents using cat, but this is incorrect way. How can I achieve this? I referred to https://unix.stackexchange.com/a/108797/431721 and tried using cat $( ls -lt *.txt | tail -n 3), but not working. Any comments?

ewr3243
  • 397

5 Answers5

3

To correctly deal with all possible filenames (including those with newlines), the following would call cat for the two least recently modified files, with the oldest file being handled last, using the zsh shell:

cat ./*.txt(.om[-2,-1])

The following would cat the two most recently modified files, with the most recently modified being handled first:

cat ./*.txt(.om[1,2])

Here, the (.om[1,2]) after the ./*.txt globbing pattern is a glob qualifier. The . in the qualifier makes the pattern only match plain files (not directories etc.). The om orders the files by modification timestamp (Om would reverse the order). The [1,2] picks out only the first two elements of the resulting list. Negative indexes here would count from the end of the list.

From the bash shell, using zsh as any other utility:

zsh -c 'cat ./*.txt(.om[-2,-1])'

and

zsh -c 'cat ./*.txt(.om[1,2])'

respectively.

Kusalananda
  • 333,661
  • Oh, much nicer than parsing ls! This uses zsh language to avoid the pipes between different tools. – Stewart Feb 05 '21 at 19:31
0

Work through it one step at a time:

$ ls -lt *.txt
-rw-r--r-- 1 stew stew 18 Feb  5 19:53 file3.txt
-rw-r--r-- 1 stew stew 18 Feb  5 19:53 file2.txt
-rw-r--r-- 1 stew stew 18 Feb  5 19:53 file1.txt

We have three files, but you only wanted two. Let's tail it:

$ ls -lt *.txt | tail -n 2
-rw-r--r-- 1 stew stew 18 Feb  5 19:53 file2.txt
-rw-r--r-- 1 stew stew 18 Feb  5 19:53 file1.txt

Ok, that's good but we really only want two filenames. ls -l isn't the right tool, we can just use ls:

$ ls -t *.txt | tail -n 2
file2.txt
file1.txt

Then put that output into cat as arguments with $():

$ cat $(ls -t *.txt | tail -n 2)
content of file 2
content of file 1

You asked about how to use an alias for ls -lthr. -l doesn't really work well because it prints more than filenames. -h only makes sense if -l is there. So here's an example for ls -tr:

$ alias lt='ls -tr'
$ cat $(lt *.txt | tail -n 2)
content of file 2
content of file 3

If you have something in your .bashrc like alias ls='ls -lthr', then you can use command ls or env ls. If you want something else that handles odd characters (such as newlines) in files too, here's an example using find instead:

cat $(find . -name '*.txt' | tail -n 2)

However ordering may not be the same as with your ls solution and it will also search subdirectories.

Stewart
  • 13,677
0

As long as you have GNU coreutils ls:

eval "sorted=( $(ls -rt --quoting-style=shell-escape *.txt) )"
cat "${sorted[@]:0:3}"

it's not an ideal solution but it will create an array with the txt files in your current directory sorted by time modified and then cat the first 3.

jesse_b
  • 37,005
0

I propose this:

arr=("$(find ~+ -type f -name "*.txt" -printf "%T@\000%p\n" | sort -nr | head -n 2 | cut -d '' -f2)")

printf "%s\n" "${arr[@]}"


# create an array from the command
arr=("$( \

use find to fetch the files and the timstamp

using null as separator

find ~+ -type f -name "*.txt" -printf "%T@\000%p\n" | \

sort

sort -nr | \

retrieve the first 2 files

head -n 2 | \

retrieve only the file names

cut -d '' -f2
)")

print the array

printf "%s\n" "${arr[@]}"

-1

You can simply execute the following command in one line:

$ ls *.txt | tail -2 | xargs cat

Explanation:

Create three test files:

tc@user1:~$ echo "first file reads 1" > file1.txt
tc@user1:~$ echo "second file reads 2" > file2.txt
tc@user1:~$ echo "third file reads 3" > file3.txt

Verify files were created:

tc@user1:~$ ls -l
total 12
-rw-r--r--    1 tc       staff           19 Feb  5 18:58 file1.txt
-rw-r--r--    1 tc       staff           20 Feb  5 18:58 file2.txt
-rw-r--r--    1 tc       staff           19 Feb  5 18:58 file3.txt

List all text files, then read the contents of the last two files by use of piping:

tc@user1:~$ ls *.txt | tail -2 | xargs cat
second file reads 2
third file reads 3