17
submitted 1 week ago by sinextitan@lemmy.world to c/linux@lemmy.ml

Ik I can use grep to find filenames with specific characters and then print that to a list but idk how to utilize said list to rename. I found another solution which's using mv but couldn't figure out how to make it work w subdirectories. I think using both grep and mv in unison's the answer just idk how to do it. plz halp

all 32 comments
sorted by: hot top controversial new old
[-] JoMiran@lemmy.ml 9 points 1 week ago

Let's see, and please do not execute this "as is" since I am pulling this out of my butt. Assuming directory name is "dir1", the file to be renamed is "crunk", and the new name is "chunk", then... (I assumed bash)

find dir1 -type f -name 'crunk' -exec bash -c 'mv "$0" "${0/crunk/chunk}"' {} \;
[-] thingsiplay@lemmy.ml 6 points 1 week ago

The the use of a bash interpreter? find -exec can run mv directly.

[-] JoMiran@lemmy.ml 2 points 1 week ago

Nice. Could you recreate my mess using it? We all carry bad habits and inneficiencies, so I'm down to learn a cleaner, more efficient way.

[-] thingsiplay@lemmy.ml 1 points 1 week ago

I was just asking why you used a bash interpreter.

[-] GaumBeist@lemmy.ml 2 points 1 week ago

Presumably so the regex in the parameter expansion/replacement works, since you can't do that to the placeholder {} string that find uses

[-] SoulKaribou@lemmy.ml 2 points 1 week ago

Yes! Bash variable expansion to the rescue!

[-] sinextitan@lemmy.world 1 points 1 week ago

for me and those that follow. could you please explain what does what?

[-] JoMiran@lemmy.ml 5 points 1 week ago

find dir1 -- find something within directory "dir1"

-type f -- specifies that what you are looking fir is a file

-name 'crunk' -- the file name is 'crunk'

-exec bash -c -- for the results, execute bash command

The command executed is move (mv)

'mv "$0" "${0/crunk/chunk}"' {} ; -- move from crunk to chunk

[-] sinextitan@lemmy.world 1 points 1 week ago
[-] GaumBeist@lemmy.ml 1 points 1 week ago* (last edited 1 week ago)

Idk how versed you are on Bash Parameter Expansion or the find command, so I'd like to expand (pun intended) a little more on Jo Miran's explanation (if you already know, I'll leave this for others who may not):

find's -exec (and -execdir) option takes everything after it until a semicolon — which usually needs to be escaped, so the shell doesn't accidentally treat it as the command separator special character — as a command to run and arguments to pass to that command. Furthermore, when using the -exec option, find treats all instances of {} as places where it should substitute the files it matched

So breaking down -exec bash -c 'mv "$0" "${0/crunk/chunk}"' {} \; really just tells find to take the name of a file it found (in this example it would only match dir1/crunk) and put it after the command bash -c 'mv "$0" "${0/crunk/chunk}"'

So now the command to run looks like bash -c 'mv "$0" "${0/crunk/chunk}"' dir1/crunk

this command spawns a (sub)shell, bash, tells it to run the next argument as a command, -c, gives it that command to run, mv "$0" "${0/crunk/chunk}", and passes filename as an argument, dir1/crunk

So now let's talk shell parameters

Usually $0 is a special parameter that references the shell (or script) that invoked the command. In the case of using the -c option, bash actually changes $0 to be the argument after the command to run: dir1/crunk

So now the command looks more like

mv "dir1/crunk" "${0/crunk/chunk}"

So let's finally get to the finish line: shell parameter expansion. Shell parameters (aka shell variables) can be written with curly braces around the name, so $SHELL and ${SHELL} refer to the same thing. But the curly braces can also let the shell know that if it sees certain special characters after the variable's name, it should do some transformations to the contents of the variable.

In this case ${0/crunk/chunk} takes the contents of $0, searches for the first instance of the string ”crunk", and replaces it with "chunk" before inserting it into the command.

So now the final command to run looks like

mv "dir1/crunk" "dir1/chunk”


Also worth mentioning that the -name option of find accepts wildcards in its argument.

I would also recommend using the -execdir option instead of -exec in this specific case, because it will run commands from inside the directories where it finds the files. In this case, that means {} would expand to ./crunk instead of dir1/crunk; this will be relevant in about 3 paragraphs.

So now you can tweak the command to your needs. If you wanted to find more than one file that, for example, all had a "u" somewhere in the name, you could do so thusly

find dir1 -name ”*u*"

And then if you wanted to change the "r" in the filenames to "l", you could do:

find dir1 -name "*u*" -type f -execdir bash -c 'mv "$0" "${0/r/l}"' {} \;

Note that you could not do this with the regular -exec option, as it would try to mv dir1/crunk dil1/crunk and throw an error because that directory (dil1) likely doesn't exist... and even if it did, you don't want your command moving files to different directories without your knowledge

Notice that it also only changed the "r" in dir1 to an ”l”, and left the "r" in "crunk" alone? That's not a typo on my part, that's the intended behavior of Shell Parameter Expansion. If you wanted to replace all "r"s in filename, you would have to change the expression to ${0//r/l} (note the double slash)

Seriously, it's worth reading that page from gnu.org. Parameter expansion can get incredibly powerful, and it's much easier to use the right format (${VAR/%r/h}) than trying to combine the most simple ones to achieve the same goal (e.g. DO NOT DO THIS: ${${VAR//r/h}/h/r}; it won't even work as intended and it's unnecessarily complex to read)

[-] sinextitan@lemmy.world 1 points 1 week ago

not versed at all. thanks for the explanation

[-] zeppo@lemmy.world 4 points 1 week ago

You can do that with the find command. Also xargs is a good one to know for stuff like this, but when commands have the capacity to work on a list already like find, you don't need xargs. You can use find -exec rename -n with a regex perhaps. I'm not a linux system currently and can't test a snippet, but I'll check back later if the question is still open.

[-] sinextitan@lemmy.world 1 points 1 week ago

I'll edit the post w the solution I find but as this can be done many ways, feel free to comment your solution.

[-] whatiswrongwithyou@lemmy.ml 2 points 1 week ago

| is usually my solution.

Quoting variables in scripts is my other go to before I actually start tracing what went wrong.

[-] HaraldvonBlauzahn@feddit.org 1 points 1 week ago* (last edited 1 week ago)

Two ways:

  1. Say you want to rename the files Sevilla_big{number}.jpg to Sevilla_small{number}.jpg:

in bash, using the extglob option:

 shopt -s extglob
for f in pics/**/Sevilla_big*.jpg
do 
     mv "${f}" "${f/big/small/}"
done

(If you want to type it in a single line, you need to add semicolons as separators.)

The '"' are only needed if file names contain whitespaces.

  1. Using find:

    find . -name 'Sevilla.*big.jpg' > t

    now, you edit the file named "t" with Emacs or vim so that you duplicate the file names, modify them as you want, and copy them using the copy-rectangle command right to the collumn with the original names. (Vim also offers this functionality, it is super useful to learn!) Delete any names of files you don't want to change. save the result to t.

Then, in bash:

 <t while read a b; do mv $a $b; done

Alternatively, you could also insert the mv command as a first collumn in t, save t and do:

source t

This variant wouldn't work with spaces in filenames.

  1. Bonus option:

Use the mmv command

[-] bjoern_tantau@swg-empire.de 1 points 1 week ago

Are you maybe looking for mmv (multimove)?

It's hard to tell what you actually want to do.

But you should not need to use grep to finde specific files. Just bash's own abilities should be enough.

For example */*a* would go into every directory and look for files with "a" in them.

For more complex operations find can also be used.

[-] sinextitan@lemmy.world 3 points 1 week ago

basically I'm mirroring my music library to my new phn and Android shits itself when it encounters ?,*," in filenames. I used to manually remove them but today I decided to actually learn how to automate.

[-] bjoern_tantau@swg-empire.de 1 points 1 week ago

Nice, extra hard because those symbols all have special meanings when searching something. Nice puzzle, I wish I had the energy to solve it right now.

[-] eleijeep@piefed.social 1 points 1 week ago

Are you just trying to remove special characters from the filename, or are you also trying to move them to new directories?

[-] sinextitan@lemmy.world 1 points 1 week ago

trying to remove them so that I can transfer to Android. I can use grep to improve my manual process but I'd love for it to be automatic.

[-] MonkderVierte@lemmy.zip 1 points 1 week ago* (last edited 1 week ago)

Btw, yt-dlp has a --restrict-filenames option. And also --extract-audio and --audio-format.

[-] sinextitan@lemmy.world 1 points 1 week ago* (last edited 1 week ago)

I didn't get my music from yt-dlp. and even if the source title contains special chars, yt-dlp uses chars that look the same but aren't.

edit: am aware of the feature but the one I mentioned does the job for me

[-] thingsiplay@lemmy.ml 1 points 1 week ago

yt-dlp has an option to rename downloaded files to restrict to ASCII characters (and another option to restrict filename length). I usually use my own wrapper script around yt-dlp, but I guess you already have your own. So for that, you can use the option --restrict-filenames to "Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames". See all options with man yt-dlp. Try it out.

[-] sinextitan@lemmy.world 1 points 1 week ago

Ik of yt-dlp being able to rename the media that I download using it. but I didn't use it to get my music. plus it's not a prob that I have w yt-dlp. even w/o yt-dlp using their same same but not same chars, Linux doesn't mind ?,* and " in filenames. but I do like the feature as / does present probs.

[-] thingsiplay@lemmy.ml 1 points 1 week ago* (last edited 1 week ago)

Here is a different approach involving loops instead. This allows for greater control over what happens. Note in this script the rename() function just echos the command without any effect. This is just a demonstration, so you can experiment with the arguments and edit the script first.

If you have only one argument that is then used to search with find, no slashes in search term is allowed. I didn't want complicate the script further and left it as it is.

#!/usr/bin/env bash

# Rename files
#
# This script has 3 ways to input filenames.
#
# If only one argument is given, then its used as search term to match
# filenames. If more than one argument
#
# is given, then instead searching for files it will rename all given
# filenames.
#
# If no filename is given, then list of files will be read from stdin pipe. In
# that case combine it with a tool like find.
#
# Change the below function rename() to set what to do with each old filename.
# The default is to echo, so change that first.
#
#
# Examples:
#
#   renameclean.sh '*txt'
#
#   renameclean.sh 'dir1/random?file.txt' 'dir2/subdir/anotherfile.zip'
#
#   find dir -type f -name '*.txt' | renameclean.sh

rename() {
    old="${1}"
    new="${old}"

    # Special characters such as ?, * or " need a backslash to match them
    # literally.
    new="${new/\?/REPLACEMENT}"
    new="${new/\*/REPLACEMENT}"
    new="${new/\"/REPLACEMENT}"

    echo mv -- "${old}" "${new}"
}

# If no argument is given, then read list from stdin.
if [ "${#}" == 0 ]; then
    while read -r file; do
        rename "${file}"
    done
# If one argument is given, then use it as a search name.
elif [ "${#}" == 1 ]; then
    find . -type f -name "${1}" -print0 |
        while read -r -d $'\0' file; do
            rename "${file}"
        done
# If more than one argument is given, then use all arguments as filenames to
# rename.
else
    for file in "${@}"; do
        rename "${file}"
    done
fi
this post was submitted on 21 Jul 2026
17 points (100.0% liked)

Linux

66746 readers
967 users here now

From Wikipedia, the free encyclopedia

Linux is a family of open source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991 by Linus Torvalds. Linux is typically packaged in a Linux distribution (or distro for short).

Distributions include the Linux kernel and supporting system software and libraries, many of which are provided by the GNU Project. Many Linux distributions use the word "Linux" in their name, but the Free Software Foundation uses the name GNU/Linux to emphasize the importance of GNU software, causing some controversy.

Rules

Related Communities

Community icon by Alpár-Etele Méder, licensed under CC BY 3.0

founded 7 years ago
MODERATORS