3

I have a folder called simulations, which contains 101 subfolders. These sub-folders are named posterior_predictive_sim_1 to posterior_predictive_sim_101.

Each subfolder contains five files: seq.nex, seq[1].nex, seq[2].nex, seq[3].nex, and seq[4].nex.

I want to delete the files seq[1].nex, seq[2].nex, seq[3].nex, and seq[4].nex from each subfolder (so that only seq.nex remains).

How do I do that from the command line?

3 Answers 3

5

With just rm:

rm simulations/posterior_predictive_sim_*/seq\[[1-4]\].nex

The escaped \[ and \] are literal square brackets. The unescaped [1-4] inside them is a glob pattern that matches the digits 1 to 4.

This only works if the glob doesn't expand to more filenames than will fit into one command-line, the ARG_MAX limit - this varies depending on the OS, but is about 2 million characters on modern Linux. ARG_MAX applies to external commands like rm, not to shell built-ins like for or echo.

Also, test this with /bin/echo instead of rm first. Because: 1. /bin/echo is safe, won't delete anything. and 2. /bin/echo is an external command, not the shell built-in version of echo so will also test if the total command length will be <= ARG_MAX.

Using GNU find:

find simulations/ -type f -name 'seq\[[1-4]\].nex' -delete

I suggest running this with -ls or -print before running it with -delete (or -exec rm as below), just to verify that it will do what you want.

Alternatively:

find simulations/ -type f -name 'seq\[[1-4]\].nex' -exec rm {} +

Some ancient versions of find don't support + at the end of the -exec predicate, so use \; instead:

find simulations/ -type f -name 'seq\[[1-4]\].nex' -exec rm {} \;

The difference between the + and the \; is that with +, find tries to fit as many filename arguments into the rm command as it can (ARG_MAX again). With \;, it runs rm once per filename - this is obviously much slower, but it still useful (e.g. when you need to run a program that only takes one filename argument).

BTW, \; isn't special to find. In fact, find itself uses just ; as a terminator for -exec. The \ is to make sure the shell passes the semi-colon on to find, i.e. prevent the shell from interpreting the ; as the end of the find statement. shell statements are separated by newlines OR semi-colons.

5
  • LOL yeah I saw that, hence the question... do you mean + isn't useful when there is only one filename as argument? :-D Mar 31, 2023 at 13:53
  • nope. 1 filename still fits into one command line. You can also use + if you want to run, say, a shell script - e.g. find ... -exec sh -c 'for f; do something with "$f"; done' sh {} +
    – cas
    Mar 31, 2023 at 13:53
  • So the only situation when it is useful is if find is too old to support +? Mar 31, 2023 at 13:55
  • or when you couldn't be bothered writing a shell script wrapper to iterate over multiple arguments.
    – cas
    Mar 31, 2023 at 13:56
  • Gotcha, thx :-) Mar 31, 2023 at 13:57
0

First you need the list of files. You can use ls, find, fd... Save this to a text file: ls posterior_predictive_sim_*/ > all.txt. People complain about parsing ls, tl;dr is it doesn't matter here.

Then you want to filter that down to those files you actually want to delete: cat all.txt | grep -v 'seq.nex' > del.txt (you can also try to fight your shell in accepting the more conservative regex pattern seq[\d+]\.nex).

Now open del.txt and check that you actually want to delete all that. You can just manually edit the file if you want.

Finally, actually delete them. rm can take many filenames at once, so unless you have many thousands of files, you could try to do that. I like to use GNU Parallel: cat del.txt | parallel --dry-run rm {}. It will print the actual commands it's planning to run, remove --dry-run to do it for real.

If you like living on the edge you can also pipe all these commands to each other instead of saving them to files. Hope you didn't make a mistake, rm'd files can't be recovered. Btw use https://github.com/andreafrancia/trash-cli instead, it lets you undelete files.

3
  • That’s the Really Inefficient Way of deleting the files.
    – RonJohn
    Apr 1, 2023 at 2:24
  • @RonJohn Really inefficient in what sense? Spending 30 sec instead of 10? Efficiency is less important here than reducing the risk that you will accidentally delete unintended files because of a typo with no way to recover. Although I can see that frequent users of the site are more excited about cute little one liners, than practical solutions that are not as braggable... Apr 3, 2023 at 15:53
  • Anyways, I didn't write the answer to impress critics, but to help OP (unusual as that may be in today's SE). If @Namenlos finds this answer not useful, I'm happy to delete it. Apr 3, 2023 at 15:59
-1

With a loop:

for i in $(ls); do rm posterior_predictive_sim_$i/seq\[*.nex; done


Leaving this up just as a reminder to myself and others why NOT to do it this way - see cas's comments below

6
  • 3
    Don't Parse ls. See also Why does my shell script choke on whitespace or other special characters? and $VAR vs ${VAR} and to quote or not to quote. Your for loop would be better written as for i in simulations/posterior_predictive_sim_*/seq\[[1-4]\].nex; do rm "$i" ; done
    – cas
    Mar 31, 2023 at 14:06
  • Ah thx, I've forgotten abt that. Gotta re-read. Mar 31, 2023 at 14:07
  • or just rm simulations/posterior_predictive_sim_*/seq\[[1-4]\].nex - a forloop isn't necessary unless you really want to run rm once per filename. or if the glob expands to more filenames than will fit in one command line (use find in that case).
    – cas
    Mar 31, 2023 at 14:13
  • rm seq\[[1-4]\].nex assumes we know how many files there are. Assuming we don't, is seq\[*.nex functionally or otherwise questionable? It does work. I.e. rm posteri*/seq\[*.nex Mar 31, 2023 at 14:17
  • there's not much difference between any of those, except seq\[*.nex might expand to more filenames. The OP said 1-4 so it's reasonable to assume that's correct.
    – cas
    Mar 31, 2023 at 14:26

You must log in to answer this question.

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