Recursively rename files to remove dots but leave extension unchanged - shell

I've got a number of folders (80?) with files in them. Some of the filenames have multiple dots (file.name.ext). With the 'find' command being recursive, I'm able to rename the filenames within the folders from uppercase to lowercase:
find . -type f -execdir rename 'y/A-Z/a-z/' {} \;
Where 'find .' indicates to search the current folder. Where 'type f' searches only for files. Where 'execdir' executes the subsequent command on the output.
To do the same (uppercase to lowercase), but for the folders, both of these work:
rename 'y/A-Z/a-z/' *
rename 'y/A-Z/a-z/' ./*
To remove the dots from the foldername, this works:
find . -maxdepth 1 -execdir sed 's/[.]/_/g' {} \;
edit:(actually this is not working for me now)
...
What fails is when I try to recursively remove the dots:
find . -type f -execdir rename 's/\.(?=[^.]*\.)/_/g' '{}' \;
I get the error:
Can't rename ./filename.ext _/filename.ext: No such file or directory
I've also tried to add -printf "%f\n" to remove the leading ./ :
find . -type f -printf "%f\n" -execdir rename 's|[.]|_|g; s|_([^_]*)$|.$1|' {} \;
which outputs the filename followed by the same error
file.name.ext
Can't rename ./file.name.ext _/file.name.ext: No such file or directory
Those commands above were run from the parent folder 'above' the 80 folders that contain the files, with the idea of doing a dryrun (rename -nono) on all the files within the 80 folders at once.
From within one of those 80 folders I can remove the dots from the filename, leaving the dot in the extension unchanged, with:
rename 's/\.(?=[^.]*\.)/_/g'
But I don't want to have to go into each of those many folders to run the command. What will work recursively to delete all dots, leaving the extension dot alone?

I found the answer here:
Linux recursively replace periods for all directorys and all but last period for files with underscores
At first I didn't think it answered my specific question, but it actually does.
while IFS= read -rd '' entry; do
entry="${entry#./}" # strip ./
if [[ -d $entry ]]; then
rename 'y/A-Z/a-z/; s/ /_/g; s/\./_/g' "$entry"
else
rename 'y/A-Z/a-z/; s/ /_/g; s/\.(?=.*\.)/_/g' "$entry"
fi
done < <(find . -iname '*' -print0)
Thanks to anubhava .

Related

Recurse through subdirectories and rename all files of a given extension the same filename [duplicate]

This question already has answers here:
Find files recursively and rename based on their full path
(3 answers)
Closed 2 years ago.
This should be simple but I am getting stuck somewhere.
I want to recurse through a directory and rename all pdfs the same filename. The renamed files should remain in their current subdirectory.
The current PDF filenames are arbitrary.
Assume that I am running this script from the top directory. Inside this top directory are several subdirs, each with a PDF with an arbitrary filename.
This works to rename the files in place:
find . -iname "*.pdf" -exec rename 's/test.pdf/commonname.pdf/' '{}' \;
But since the current filenames are arbitrary, I need to swap out a regex for "any characters or digits" in place of test.pdf
My understanding is that the correct regex expression is .*
So I tried:
find . -iname "*.pdf" -exec rename 's/.*/commonname.pdf/' '{}' \;
When I run this, the first PDF gets renamed to commonpdf.pdf, but it is moved into the top directory. My use case requires that the PDFs get renamed in place.
I am missing something obvious here, clearly - can you spot my mistake?
The problem is that in s/.*/commonname.pdf/, .* matches the complete path, not just the filename. You could make sure that the regular expression applies to nothing but the filename by matching on non-slashes:
find . -iname '*.pdf' -exec rename 's|[^/]*$|commonname.pdf|' '{}' \;
or you could use GNU find's -execdir, which sets the working directory to the directory containing the matching file:
find . -iname '*.pdf' -execdir rename 's/.*/commonname.pdf/' '{}' \;
or not use rename at all:
find . -iname '*.pdf' -execdir mv {} commonname.pdf \;
or not use find, but a single invocation of rename:
rename 's|[^/]*$|commonname.pdf|' **/*.pdf
This requires the globstar shell option to enable the ** glob.
Use the -n option to rename for a dry run without actually changing filenames.

Find and rename multiple files using a bash script in Linux

As an example, in a directory /home/hel/files/ are thousends of files and hundreds of directories.
An application saves there its output files with special characters in the file names.
I want to replace these special characters with underscores in all file names. e.g. -:"<>#
I wrote a bash script which simply repeats a command to rename the files using Linux/Unix 'rename'.
Example: file name: rename.sh
#!/bin/bash
rename "s/\'/_/g" *
rename 's/[-:"<>#\,&\s\(\)\[\]?!–~%„“;│\´\’\+#]/_/g' *
rename 'y/A-Z/a-z/' *
rename 's/\.(?=[^.]*\.)/_/g' *
rename 's/[_]{2,}/_/g' *
I execute the following find command:
find /home/hel/files/ -maxdepth 1 -type f -execdir /home/hel/scripts/rename.sh {} \+
Now the issue:
This works fine, except the fact, that it renames subdirectories too, if they have the searched characters in their name.
The find command searches just for files and not for directories.
I tried some other find variations like:
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} \+
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} +
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} \;
They are all working, but with the same result.
What is not working:
find /home/hel/files/ -maxdepth 1 -type f -exec sh /home/hel/scripts/rename.sh {} \+
This one is dangerous, because it renames the directories and files in the current directory, where you call the find command too.
Maybe one has an idea, why this happens or has a better solution.
The script rename.sh did not use its command line arguments at all, but instead searched files and directories (!) on its own using the glob *.
Change your script to the following.
#!/bin/bash
rename -d s/\''/_/g;
s/[-:"<>#\,&\s\(\)\[\]?!–~%„“;│\´\’\+#]/_/g;
y/A-Z/a-z/;
s/\.(?=[^.]*\.)/_/g;
s/[_]{2,}/_/g' "$#"
Then use find ... -maxdepth 1 -type f -exec sh .../rename.sh {} +.
Changes Made
Use "$#" instead of * to process the files given as arguments rather than everything in the current directory.
Execute rename only once as a 2nd rename wouldn't find the files specified with "$#" after they were renamed by the 1st rename.
Use the -d option such that only the basenames are modified. find always puts a path in front of the files, at the very least ./. Without this option rename would change ./filename to mangledPath/newFilename and therefore move the file to another directory.
Note that man rename is a bit misleading
--path, --fullpath
Rename full path: including any directory component. DEFAULT
-d, --filename, --nopath, --nofullpath
Do not rename directory: only rename filename component of path.
For a given path rename -d 's...' some/path/basename just processes the basename and ignores the leading components some/path/. If basename is a directory it will still be renamed despite the -d option.

Rename all files in directory and subdirectory

How do I rename files in directory and subdirectory?
I found this program, but I need to go change files in subdirectory.
for file in *#me01
do
mv "$file" "${file/#me01/_me01}"
done
n#me01
to
n_me01
The following one-liner will likely work for you:
find . -type f -name '*#me01' -execdir rename '#me01' '_me01' {} \;
The following form is likely more correct as it will change only the last # to _ if there are multiple occurrences of #me01 in the file:
for f0 in $(find . -type f -name '*#me01')
do
f1=$(printf '%s' "$f0" | sed 's/#me01$/_me01/')
mv "$f0" "$f1"
done
This latter form is also more flexible and can be built upon more easily as the regex language in sed is much more powerful than rename expressions.
If rename of directories is also required the following can easily be added...
Either:
find . -type d -name '*#me01' -execdir rename '#me01' '_me01' {} \;
Or:
for d0 in $(find . -type d -name '*#me01')
do
d1=$(printf '%s' "$d0" | sed 's/#me01$/_me01/')
mv "$d0" "$d1"
done
Using bash:
shopt -s globstar
for name in **/*#me01; do
mv "$name" "${name%#me01}_me01"
done
This enables the globstar shell option in bash which makes ** match across path separators in pathnames.
It also uses a standard parameter substitution to delete the #me01 portion at the very end of the found pathname and replace it with _me01.

Find files with spaces in the bash

I want to search for files in a folder which have a space in its filenames, f.e.
/vol1/apache2/application/current/Test 1.pdf
/vol1/apache2/application/current/Test 2.pdf
I know there's a find command but I can't figure out the correct parameters to list all those files.
Use find command with a space between two wildcards. It will match files with single or multiple spaces. "find ." will find all files in current folder and all the sub-folders. "-type f" will only look for files and not folders.
find . -type f -name "* *"
EDIT
To replace the spaces with underscores, try this
find . -type f -name "* *" | while read file; do mv "$file" ${file// /_}; done
With find:
find "/vol1/apache2/application/current" -type f -name "*[[:space:]]*"
The following worked for me to find all files containing spaces:
find ./ -type f | grep " "
To also rename all found files to the same filename without the spaces, run the following:
find ./ -type f | grep " " | while read file; do mv "$file" ${file// }; done
Ways to configure the above script:
To rename only directories, change -type f to -type d
To use git-aware rename, change do mv to do git mv
To rename differently, change ${file// }; to ${file// /[some-string]};. For example, to replace the spaces with "_", use: ${file// /_}; (notice the leading "/")

How to move files en-masse while skipping a few files and directories

I'm trying to write a shell script that moves all files except for the ones that end with .sh and .py. I also don't want to move directories.
This is what I've got so far:
cd FILES/user/folder
shopt -s extglob
mv !(*.sh|*.py) MoveFolder/ 2>/dev/null
shopt -u extglob
This moves all files except the ones that contain .sh or .py, but all directories are moved into MoveFolder as well.
I guess I could rename the folders, but other scripts already have those folders assigned for their work, so renaming might give me more trouble. I also could add the folder names but whenever someone else creates a folder, I would have to add its name to the script or it will be moved as well.
How can I improve this script to skip all folders?
Use find for this:
find -maxdepth 1 \! -type d \! -name "*.py" \! -name "*.sh" -exec mv -t MoveFolder {} +
What it does:
find: find things...
-maxdepth 1: that are in the current directory...
\! -type d: and that are not a directory...
\! -name "*.py: and whose name does not end with .py...
\! -name "*.sh: and whose name does not end with .sh...
-exec mv -t MoveFolder {} +: and move them to directory MoveFolder
The -exec flag is special: contrary to the the prior flags which were conditions, this one is an action. For each match, the + that ends the following command directs find to aggregate the file name at the end of the command, at the place marked with {}. When all the files are found, find executes the resulting command (i.e. mv -t MoveFolder file1 file2 ... fileN).
You'll have to check every element to see if it is a directory or not, as well as its extension:
for f in FILES/user/folder/*
do
extension="${f##*.}"
if [ ! -d "$f" ] && [[ ! "$extension" =~ ^(sh|py)$ ]]; then
mv "$f" MoveFolder
fi
done
Otherwise, you can also use find -type f and do some stuff with maxdepth and a regexp.
Regexp for the file name based on Check if a string matches a regex in Bash script, extension extracted through the solution to Extract filename and extension in Bash.

Resources