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

Comments on Rename multiple files which have a variable suffix

Parent

Rename multiple files which have a variable suffix

+1
−0

I compressed some JPEGs with curtail it messed up the filenames. It was supposed to only add -min at the end but ended up adding a random string after the extension 😠:

prs@PC:/DOWNLOADS/Pictures$ find . -type f
./IMG_20230917_093726_2-min.jpg-U2XlUA   <<< To rename "[...]_2-min.jpg"
./IMG_20230917_093726_2.jpg              - Don't do anything
./IMG_20230917_093738_3-min.jpg-H39QsD   <<< To rename "[...]_3-min.jpg"
./IMG_20230917_093738_3.jpg              - Don't do anything
./IMG_20230917_094057_1-min.jpg-AbxJMt   <<< To rename "[...]_1-min.jpg"
./IMG_20230917_094057_1.jpg              - Don't do anything

How can I remove the random string for each -min file?

I already have a find command that gets the -min files while excluding the pictures I don't want to touch:

find . -name "*-min.jpg-*"
History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

Post
+1
−0
find . -type f -print0 \
| grep -z -- '-min.jpg-[[:alnum:]]*$' \
| while IFS= read -r -d '' f; do
    find "$f" -print0 \
    | sed -z 's/-min.jpg-[[:alnum:]]*$/-min.jpg/' \
    | xargs -0 mv "$f";
done;

Or, if you prefer a one-liner:

find . -type f -print0 | grep -z -- '-min.jpg-[[:alnum:]]*$' | while IFS= read -r -d '' f; do find "$f" -print0 | sed -z 's/-min.jpg-[[:alnum:]]*$/-min.jpg/' | xargs -0 mv "$f"; done;

The code above is hardened against malicious, broken, or otherwise non-portable file names. That makes it slightly more complex than necessary.

Never use whitespace in file names. They are non-portable. See POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282.

For the hardened version, find(1)'s -print0 and xargs(1)'s -0 are common enough that don't need explanation (do an online search). grep(1)'s and sed(1)'s -z and read(1)'s -r are less common, but still common enough. read(1)'s -d '' is a trick explained here: https://stackoverflow.com/questions/9612090/how-to-loop-through-file-names-returned-by-find#comment98776168_9612232.

Here's a simpler version, which you can run if you know your file names are portable:

find . -type f \
| grep -- '-min.jpg-[[:alnum:]]*$' \
| while read f; do
    echo $f \
    | sed 's/-min.jpg-[[:alnum:]]*$/-min.jpg/' \
    | xargs mv $f;
done;

find . -type f |:

Find files

| grep -- '-min.jpg-[[:alnum:]]*$' |:

Filter those that have interesting file names. -- is necessary to avoid interpreting the pattern as an option to grep(1).

| while read f; do:

For each path name (each line), store the path name in $f, and run the nested commands on it.

echo $f |:

Echo the full path name.

| sed 's/-min.jpg-[[:alnum:]]*$/-min.jpg/' |:

Remove the part of the file name that we don't like.

| xargs mv $f;:

Move the file $f (this was the old path name), to the path resulting of the previous filter.

done;:

Have a nice sleep :)

History
Why does this post require moderator attention?
You might want to add some details to your flag.

1 comment thread

Why not `find . -type f -name '*-min.jpg-*' -print0` to avoid the `grep`? And there is no reason to a... (7 comments)
Why not `find . -type f -name '*-min.jpg-*' -print0` to avoid the `grep`? And there is no reason to a...
terdon‭ wrote 7 months ago

Why not find . -type f -name '*-min.jpg-*' -print0 to avoid the grep? And there is no reason to assume the random string will only have alphanumerical values. Also note that this has nothing to do with portability. All *nix systems accept everything except / and \0 in file names.

alx‭ wrote 7 months ago · edited 7 months ago

And there is no reason to assume the random string will only have alphanumerical values.

That was my inductive guess from the small sample. If it's different, the regex could be adjusted to fit. The good thing of using [[:alnum:]] is that it self-documents, so any mismatches will be easily debuggable.

I prefer being specific, and not using a wild .*, because I prefer to fall on the side of doing less than wanted, instead of having slight chances of doing more than wanted.

alx‭ wrote 7 months ago · edited 7 months ago

All *nix systems accept everything except / and \0 in file names.

That's true for the kernels. Individual programs may or may not. When writing scripts like this one (which I do often), it helps not having to consider such insane file names. Usual *nix filters do not like non-portable file names, so it's better to stick to POSIX portable file names.

alx‭ wrote 7 months ago · edited 7 months ago

Why not find . -type f -name '*-min.jpg-*' -print0 to avoid the grep?

Because the grep(1) is so much simpler conceptually. I don't need to remember of all the options from find(1) that can be used for file names (-ilname, -iname, -ipath, -iregex, -iwholename, -lname, -name, -path, -regex, -wholename). This means checking the manual page every now and then to remember some details.

See also: http://doc.cat-v.org/unix/find-history

I only need to remember how grep(1) works. Since I already need to remember how grep(1) works for many other reasons, that's already no work.

Also, regexes are usually more powerful and precise than globs, which are harder when you need a complex expression.

Let me ask you the converse question: when we have grep(1), a simple and versatile tool to do exactly that, filtering text based on patterns, why use options to specific commands to do the same thing? Why use find(1)'s -name glob when you can just pipe to grep(1)?

alx‭ wrote 7 months ago · edited 7 months ago

All *nix systems accept everything except / and \0 in file names.

And great news, POSIX may outlaw \n (and a few more) soon: https://www.austingroupbugs.net/view.php?id=251.

terdon‭ wrote 6 months ago

Fair enough. I tend to avoid using a second tool for the overhead (re why not use grep) but I agree that it is a very minor concern in this context. Glad to hear about the upcoming POSIX thing, but we will still need to handle newlines for a few years to deal with backwards compatibility. And anyway, for now, sadly, \n is allowed in file names. But meh, I do agree this is nitpicking on what is really an excellent answer.

alx‭ wrote 6 months ago · edited 6 months ago

terdon‭ No problem. Nitpicking helps get even better answers, or just random useful info. :)

You may find it interesting that find(1), because of the library it uses for name filtering, is very slow, and grep(1), even with the overhead of a new fork(2), outperforms find(1) very easily. You can have a look at the discussion here: https://github.com/sharkdp/fd/issues/1395.