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?

6
  • 4
    Note for those that may be surprised to see a -U where that option means unsorted in GNU ls, the OP is probably using a BSD system where -U means to use the creation time instead of last modification time. – Stéphane Chazelas May 16 '17 at 13:48
  • Yeah, I'm using OS X for this particular script. – demcooltricks May 16 '17 at 20:55
  • Has --libxo made it to macOS? – Stéphane Chazelas May 16 '17 at 20:56
  • See Warning regarding ‘>’ in the answers of the above question. – muru May 17 '17 at 7:08
  • Also duplicate but from another site: askubuntu.com/q/728382/295286 Basically the answer is that redirections are always done first. – Sergiy Kolodyazhnyy May 17 '17 at 8:41
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"

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 --

Not the answer you're looking for? Browse other questions tagged or ask your own question.