3

For a bash script I'm making, I need to delete all but the newest 2 files in a directory.

I decided to use ls -tUr > somefile.txt and then just tail the newest 2, move them somewhere else, rm *, and move them back.

For some reason, somefile.txt actually shows up (among the rest of the files) inside somefile.txt which screws up using the tail command.

So my question is, logically ls would return the current folder/files and then be redirected into somefile.txt, but clearly this isn't happening since somefile.txt must exist before ls runs; why is this?

2 Answers2

8

This answers the question "why does this happen?"

Yes, somefile.txt will actually be created before ls is run.

$ utility >file

The first thing that happens is that the shell notices the redirection, creates file (or truncates it if it already exists), then it executes utility with its standard output stream going into file.

The utility is not generally aware of or concerned with where its standard output is going (some, like ls, do check to see whether it's a TTY or not and changes their behaviour accordingly), so it doesn't matter whether it's writing to a regular file, pipe, device file or socket. It's certainly not concerned with creating this file. It is therefore the job of the shell to make sure that the plumbing is in place before the process is started, which includes creating or truncating the file named file in the example above.

For answers dealing with your issue regarding deleting all but the newest files, see the question "remove oldest files"

Kusalananda
  • 333,661
2

While Kusalananda has answered your question about why somefile.txt appears in the output of ls, I'll try and address the question about how to delete all but the two most recently created files as the linked question deals with most recently modified files instead.

First, you don't have to write the output in a file. Commands can interact with each other directly, that's what pipes are for.

Here, you could do:

ls -tU | tail -n +3 | xargs echo rm -f -- # echo for dry-run

But that's flawed in the general case as tail works on lines and xargs works on (possibly quoted) words, and file names are not guaranteed to be either of those. They're not even guaranteed to be valid text. It would only work properly if none of the file names contain blanks, newlines, quotes, backslashes or byte sequences not forming valid characters.

If the system is FreeBSD (as your usage of -U suggests), you could use the json output format that is reliably parsable like:

ls --libxo=json -Ut |
  perl -MJSON::PP -l0 -0777 -ne '
    @files = map {$_->{name}} @{
      decode_json($_)->{"file-information"}->{directory}->[0]->{entry}
    };
    print for @files[2..$#files]' | xargs -0 echo rm -f --