Find and rename directories on the basis of specific characters - bash

My directory tree looks like this:
../files/Italy.Pictures.Tom.Canon.2017-April/
../files/Italy.Videos.Marie.Sony.2017-April/
../files/Spain.Pictures.Food.John.iPhone.2018-September/
and so on..
My code:
#!/bin/bash
DIR="/home/user/files/"
find $DIR -depth -name "* *" -execdir rename 's/ /./g' "{}" \; # replace space with dot
find $DIR -depth -iname "*.iphone*" -execdir rename 's/.iphone//ig' "{}" \; # remove 'iPhone' from dir name
find $DIR -depth -iname "*.john*" -execdir rename 's/.john//ig' "{}" \;
find $DIR -depth -iname "*.tom*" -execdir rename 's/.tom//ig' "{}" \;
find $DIR -depth -iname "*-april*" -execdir rename 's/-april//ig' "{}" \;
find $DIR -depth -iname "*-september*" -execdir rename 's/-september//ig' "{}" \;
and more commands like this for all names, month,..
Yes, this works!
But: Is this the best way to remove/replace characters in directory names? Any suggestions to make my script more efficient? Maybe, to put all words in a list, which should be removed?
Thanks for your thoughts!

Personally, I'd prefer using for loop with sed and mv to rename directories, instead of using find and rename. For example:
#!/usr/bin/env bash
for dir in $(ls -d ./*/); do
newdir=$(sed 's/-.*$//' <<< "$dir" | sed 's/.\(iphone\|tom\|john\)//gi')
mv "$dir" "$newdir"
done
The first sed is to remove the month name. The 2nd sed will remove all names, and it can be extended by adding other names.
I don't know if it's "the best" way to do the job. However, I find it's quite efficient and easy to maintain. Hope you would like this method as well.

Related

Bash script to move all png files in folder and its subfolders to another directory?

In ~/Desktop/a/ , I have .png files, and there are also subfolders within this that also have .png files.
I'd like to move all of those .png files to another folder.
This is my code so far. It runs, but nothing is placed into the target folder. What is the problem?
#!/bin/bash
cd ~/Desktop/a/
for f in $(find . -type f -name "*.png")
do
mv $f ~/Desktop/new/
done
I guess that these image filenames maybe include spaces or other special characters.
find ~/Desktop/a/ -type f -name "*.png" -exec mv "{}" ~/Desktop/new/ \;
or
find ~/Desktop/a/ -type f -name "*.png" -print0 | xargs -0 -I{} mv "{}" ~/Desktop/new/
If your bash is new enough, you can also use globstar:
cd ~/Desktop/a || exit 1
shopt -s globstar
mv -- **/*.png ~/Desktop/new
Or (if there are too many files to fit in a single command line):
shopt -s globstar
for f in ~/Desktop/a/**/*.png; do
mv -- "$f" ~/Desktop/new
done

Shell generic equivalent of Bash Substring replacement ${foo/a/b}

Is there a shell-independent equivalence of Bash substring replacement:
foo=Hello
echo ${foo/o/a} # will output "Hella"
Most of the time I can use bash so that is not a problem, however when combined with find -exec it does not work. For instance, to rename all .cpp files to .c, I'd like to use:
# does not work
find . -name '*.cpp' -exec mv {} {/.cpp$/.c}
For now, I'm using:
# does work, but longer
while read file; do
mv "$file" "${file/.cpp$/.c}";
done <<< $(find . -name '*.cpp')
Ideally a solution that could be used in scripts is better!
Using find and -exec you can do this:
find . -name '*.cpp' -exec bash -c 'f="$1"; mv "$f" "${f/.cpp/.c}"' - '{}' \;
However this will fork bash -c for each filename so using xargs or a for loop like this is better for performance reasons:
while IFS= read -d '' -r file; do
mv "$file" "${file/.cpp/.c}"
done < <(find . -name '*.cpp' -print0)
Btw, an alternative to using bash would be to use rename. If you have the cool version of the rename command, which is shipped along with perl you can do:
find -name '*.cpp' -exec rename 's/\.cpp$/.c/' {} +
The above example assumes that you have GNU findutils, having this you don't need to pass the current directory since it is the default. If you don't have GNU findutils, you need to explicitly pass it:
find . -name '*.cpp' -exec rename 's/\.cpp$/.c/' {} +

"find" command with a variable directory

I'm trying to list the files in a directory that is given by the variable DIR. My code looks like this so far:
for i in `find $DIR -name "*.txt"
The variable DIR is already defined. I'm not sure what the syntax is here.
ls "${DIR}/*.txt"
or
find "${DIR}" -name "*.txt"
should do the trick. The first one only lists *.txt files in the directory itself, the second one also *.txt files in subdirectories.
I guess you want to execute a given action on all files with extension "txt" under $DIR and/or its subdirs. As usual there are different solutions.
This one:
$ for i in $(find "$DIR" -name \*.txt) ; do echo "Do something with ${i}" ; done
won't work if file path (either the file itself or one subdirectory) contains spaces.
But you can use this:
$ find "$DIR" -type f -name \*.txt | while read i ; do echo "Do something with ${i}" ; done
or this:
$ find "$DIR" -type f -name \*.txt -print0 | xargs -0 -I {} echo "Do something with {}"
or this:
$ find "$DIR" -type f -name \*.txt -exec echo "Do something with {}" \;
or... 100 additional solutions.
Not sure what you want.
find $DIR -name "*.txt" -print
will list all the files that end with .txt and are located in $DIR or its subdirectories. You can omit the -print as that is the default behaviour anyway.
If you have a simple thing you want to do with this file, you can use find's -exec function:
find $DIR -name "*.txt" -exec wc -l {} \;
Or you can use a loop:
for f in `find $DIR -name "*.txt"`; do
wc -l $f
mv $f /some/other/dir/
fi
note: as #mauro helpfully pointed out, this will not work if the DIR or the file names contain spaces.
Cheers

BASH: find and rename files & directories

I would like to replace :2f with a - in all file/dir names and for some reason the one-liner below is not working, is there any simpler way to achieve this?
Directory name example:
AN :2f EXAMPLE
Command:
for i in $(find /tmp/ \( -iname ".*" -prune -o -iname "*:*" -print \)); do { mv $i $(echo $i | sed 's/\:2f/\-/pg'); }; done
You don't have to parse the output of find:
find . -depth -name '*:2f*' -execdir bash -c 'echo mv "$0" "${0//:2f/-}"' {} \;
We're using -execdir so that the command is executed from within the directory containing the found file. We're also using -depth so that the content of a directory is considered before the directory itself. All this to avoid problems if the :2f string appears in a directory name.
As is, this command is harmless and won't perform any renaming; it'll only show on the terminal what's going to be performed. Remove echo if you're happy with what you see.
This assumes you want to perform the renaming for all files and folders (recursively) in current directory.
-execdir might not be available for your version of find, though.
If your find doesn't support -execdir, you can get along without as so:
find . -depth -name '*:2f*' -exec bash -c 'dn=${0%/*} bn=${0##*/}; echo mv "$dn/$bn" "$dn/${bn//:2f/-}"' {} \;
Here, the trick is to separate the directory part from the filename part—that's what we store in dn (dirname) and bn (basename)—and then only change the :2f in the filename.
Since you have filenames containing space, for will split these up into separate arguments when iterating. Pipe to a while loop instead:
find /tmp/ \( -iname ".*" -prune -o -iname "*:*" -print \) | while read -r i; do
mv "$i" "$(echo "$i" | sed 's/\:2f/\-/pg')"
Also quote all the variables and command substitutions.
This will work as long as you don't have any filenames containing newline.

rename folder/directory recursively

I want to rename folder/directory names recursively and found this solution on SO. However this command has no effect
find . -type f -exec rename 's/old/new/' '{}' \;
Is that a correct command?
find . -depth -name '*a_*' -execdir bash -c 'mv "$0" "${0//a_/b_}"' {} \;
The -depth switch is important so that the directory content is processed before the directory itself! otherwise you'll run into problems :).
100% safe regarding filenames with spaces or other funny symbols.

Resources