Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Post History

81%
+7 −0
Q&A Recursively remove files with the same name as the ones that end in `.part`

It is incorrect for two reasons. 1. File names containing glob characters This is an edge case scenario. Consider this structure: . ├── abc ├── abc.part ├── cde └── c*e.part The outermos...

posted 2y ago by Quasímodo‭  ·  edited 2y ago by Quasímodo‭

Answer
#2: Post edited by user avatar Quasímodo‭ · 2023-02-11T15:37:29Z (almost 2 years ago)
Recursive
  • It is incorrect for several reasons.
  • ### You did not restrict Find's depth
  • It will thus descend into subdirectories and delete matching files there too.
  • Easily solvable by adding `-maxdepth 1` to each Find.
  • ### File names containing [glob characters](https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html)
  • This is an edge case scenario.
  • Consider this structure:
  • ```
  • .
  • ├── abc
  • ├── abc.part
  • ├── cde
  • └── c*e.part
  • ```
  • The outermost Find will find
  • - `abc.part`, so `base=abc` and the innermost Find looks for files matching the glob `abc*`, which matches the `abc` file. Good.
  • - `c*e.part`, so `base=c*e` and the innermost Find looks for files matching the glob `c*e*`, which matches the `cde` file. Bad, because `cde` does not contain `c*e`.
  • ### File names with extra characters
  • If you have `abcde` and `abc.part` files, the former will be deleted because it matches `abc*` as should be clear from the previous case discussion.
  • This particular problem would be easily fixed by changing `$base*` -> `$base.*`.
  • ## Solutions
  • You only want to operate in the current directory, so why not
  • for f in *.part; do
  • rm "${f%.part}."*
  • done
  • Here it is crucial **not** to quote the `*`, because we want it to act as a glob (and not literally).
  • `${f%suffix}` is a special form of [parameter expansion](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html) available in all POSIX shells. It means "remove `suffix` from the expansion of `f`". It looks cleaner than Basename and doesn't spawn an extra process.
  • For something more along the lines you suggested,
  • find . -maxdepth 1 -name '*.part' -exec sh -c 'rm "$(basename "$1" .part)".*' sh {} \;
  • But it looks considerably convoluted and `-maxdepth 1` is not a [POSIX specified option to Find](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html), so you are also sacrificing portability. (It is possible to write the POSIX equivalent of `-maxdepth 1` but it adds obscurity.)
  • It is incorrect for two reasons.
  • ### 1. File names containing [glob characters](https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html)
  • This is an edge case scenario.
  • Consider this structure:
  • ```
  • .
  • ├── abc
  • ├── abc.part
  • ├── cde
  • └── c*e.part
  • ```
  • The outermost Find will find
  • - `abc.part`, so `base=abc` and the innermost Find looks for files matching the glob `abc*`, which matches the `abc` file. Good.
  • - `c*e.part`, so `base=c*e` and the innermost Find looks for files matching the glob `c*e*`, which matches the `cde` file. Bad, because `cde` does not contain `c*e`.
  • ### 2. File names with extra characters
  • If you have `abcde` and `abc.part` files, the former will be deleted because it matches `abc*` as should be clear from the previous case discussion.
  • This particular problem would be easily fixed by changing `$base*` -> `$base.*`.
  • ## Proposed solution
  • Point 1 is the real challenge: It is quite involved to feed the file names back again into another Find's `-name` argument _and_ escape the meta-characters, which is always a mine field.
  • I propose instead to use a shell with support for `**`, the recursive glob, for example Bash or Ksh with `globstar` option set or Zsh.
  • #!/bin/bash
  • shopt -s globstar #Not needed in Zsh
  • for f in ./**/*.part; do
  • rm ./**/"$(basename "$f" .part)".*
  • done
  • For a breakdown,
  • - In line 2, **`**/*.part`** matches `./a.part` but also `./a/b/c.part` (hence "recursive glob").
  • - In line 3, **`"$(basename "$f" .part)"`** removes all directory components of the file name and its `.part` extension. This would boil down to `a` and `c` in our example.
  • So the full line **`rm ./**/"$(basename "$f" .part)".*`** recursively removes files matching the `a.*` and `c.*` patterns.
  • It is crucial **not** to quote the `*` characters in the example, because we want it to act as a glob (and not to be parsed literally).
#1: Initial revision by user avatar Quasímodo‭ · 2023-01-13T21:19:19Z (almost 2 years ago)
It is incorrect for several reasons.

### You did not restrict Find's depth

It will thus descend into subdirectories and delete matching files there too.

Easily solvable by adding `-maxdepth 1` to each Find.

### File names containing [glob characters](https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html)

This is an edge case scenario.

Consider this structure:

```
.
├── abc
├── abc.part
├── cde
└── c*e.part
```

The outermost Find will find

- `abc.part`, so `base=abc` and the innermost Find looks for files matching the glob `abc*`, which matches the `abc` file. Good.
- `c*e.part`, so `base=c*e` and the innermost Find looks for files matching the glob `c*e*`, which matches the `cde` file. Bad, because `cde` does not contain `c*e`.

### File names with extra characters

If you have `abcde` and `abc.part` files, the former will be deleted because it matches `abc*` as should be clear from the previous case discussion.

This particular problem would be easily fixed by changing `$base*` -> `$base.*`.

## Solutions

You only want to operate in the current directory, so why not

    for f in *.part; do
        rm "${f%.part}."*
    done

Here it is crucial **not** to quote the `*`, because we want it to act as a glob (and not literally).

`${f%suffix}` is a special form of [parameter expansion](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html) available in all POSIX shells. It means "remove `suffix` from the expansion of `f`". It looks cleaner than Basename and doesn't spawn an extra process.

For something more along the lines you suggested,

    find . -maxdepth 1 -name '*.part' -exec sh -c 'rm "$(basename "$1" .part)".*' sh {} \;

But it looks considerably convoluted and `-maxdepth 1` is not a [POSIX specified option to Find](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html), so you are also sacrificing portability. (It is possible to write the POSIX equivalent of `-maxdepth 1` but it adds obscurity.)