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?

9
  • so, how can I display all/last n lines of the last 3 files found by ls? Commented Feb 5, 2021 at 18:51
  • ls *.txt | tail -2 | xargs -I xx cat xx may do what you want. Commented Feb 5, 2021 at 18:54
  • 3
    Why you shouldn't parse ls Commented Feb 5, 2021 at 18:56
  • What is the end goal, are you just trying to concatenate two random files or are you looking for specific files? Commented Feb 5, 2021 at 18:58
  • @jesse_b end goal is to I display all/last n lines of the last 3 files found by ls Commented Feb 5, 2021 at 19:00

5 Answers 5

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.

1
  • Oh, much nicer than parsing ls! This uses zsh language to avoid the pipes between different tools. Commented Feb 5, 2021 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.

5
  • How to use any aliases for ls -lthr with flags inside $()? Commented Feb 5, 2021 at 19:04
  • You wouldn't want ls -l for reasons I mentioned in the post. ls -h only works with -l, so don't use this in this case either. I've updated the question to answer about using ls -tr as an alias Commented Feb 5, 2021 at 19:12
  • This will fail with file names with spaces. Commented Feb 5, 2021 at 19:25
  • @Stewart your command cat $(find . -name '*.txt' | tail -n 2) gives me cat: ./foo: No such file or directory for file 'foo bar.txt' Commented Feb 5, 2021 at 19:33
  • @schrodigerscatcuriosity ah, I see. You're right. We can solve this with find -print0 and xargs -0, but then we lose the tail. I do like kulsalananda's answer here. Commented Feb 5, 2021 at 19:40
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.

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
  • Doesn't work with files with newlines in the name. Commented Feb 5, 2021 at 20:34
-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
2
  • Gives error cat: invalid option -- 'r' Commented Feb 5, 2021 at 19:07
  • 1
    @ewr3243 thats because you modified his command and added the -l option to ls. But this also wouldn't work anyway. Commented Feb 5, 2021 at 19:08

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.