Find files, rename in place unix bash - bash

This should be relatively trivial but I have been trying for some time without much luck.
I have a directory, with many sub-directories, each with their own structure and files.
I am looking to find all .java files within any directory under the working directory, and rename them to a particular name.
For example, I would like to name all of the java files test.java.
If the directory structure is a follows:
./files/abc/src/abc.java
./files/eee/src/foo.java
./files/roo/src/jam.java
I want to simply rename to:
./files/abc/src/test.java
./files/eee/src/test.java
./files/roo/src/test.java
Part of my problem is that the paths may have spaces in them.
I don't need to worry about renaming classes or anything inside the files, just the file names in place.
If there is more than one .java file in a directory, I don't mind if it is overwritten, or a prompt is given, to choose what to do (either is OK, it is unlikely that there are more than one in each directory.
What I have tried:
I have looked into mv and find; but, when I pipe them together, I seem to be doing it wrong. I want to make sure to keep the files in their current location and rename, and not move.

The GNU version of find has an -execdir action which changes directory to wherever the file is.
find . -name '*.java' -execdir mv {} test.java \;
If your version of find doesn't support -execdir then you can get the job done with:
find . -name '*.java' -exec bash -c 'mv "$1" "${1%/*}"/test.java' -- {} \;

If your find command (like mine) doesn't support -execdir, try the following:
find . -name "*.java" -exec bash -c 'mv "{}" "$(dirname "{}")"/test.java' \;

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.

Trying to find files containing an identifier, then move them to a new directory within terminal

I'm a beginner with this stuff and seem to be running into an issue.
Basically, I have many files with names containing a keyword (let's call it "Category1") within a directory. For example:
ABC-Category1-XYZ.txt
I'm trying to move them from a directory into another directory with the same name as the keyword.
I started with this:
find /path_A -name "*Category1*" -exec mv {} /path_A/Category1 \;
It spit out something like this:
mv: rename /path_A/Category1 to /path_A/Category1/Category1: Invalid
Argument
So I did some fiddling and hypothesized that the problem was caused by the command trying to move the directory Category1 into itself(maybe). I decided to exclude directories from the search so it would only attempt to move files. I came up with this:
find /path_A -name "*Category1*" \(! -type d \) -exec mv {} /path_A/Category1 \;
This did move the files from their original location to where I wanted them, but it still gave me something like:
mv: /path_A/Category1/ABC-Category1-XYZ.txt and
/path_A/Category1/ABC-Category1-XYZ.txt are identical
I'm no expert, so I could be wrong... but I believe the command is trying to find and move the files from their original directory, then find them again. The directory Category1 is a subdirectory of the starting point, /path_A, So i believe it is finding the files it just moved in the directory Category1 and attempting to move them again.
Can anyone help me fix this issue?
You are creating new files that find tries to process. Safest approach is to move them somewhere else not in the path_A you are searching with find.
Or you can use prune to ignore that directory if you don't have any other directory matching:
find /path_A -name '*Category1*' -prune -type f -exec mv {} /path_A/Category1/ \;
Although another post has been accepted, let me post a proper answer.
Would you please try:
find /path_A -name 'Category1' -prune -o -type f -name '*Category1*' -exec mv -- {} /path_A/Category1/ \;
The option -prune is rather a command than a condition. It tells find to
ignore the directory tree specified by the conditions before -prune.
In this case it excludes the directory Category1 from the search.
The following -o is logical OR and may be interpreted something like instead or else. The order of the options makes difference.
Please be noticed the 1st category1 is the directory name to exclude and the 2nd *Category1* is the filenames to find.
If you are not sure which files are the result of find, try to execute:
find /path_A -name 'Category1' -prune -o -type f -name '*Category1*' -print
then tweak the options to see the change of output.

Mac Terminal command move files from subdirectory to parent

On my Mac I amm trying to move hundreds of files on my NAS drive, from a parent directory with a load of subdirectories (and possibly directories inside them) and put all of the files into one folder.
They don't have the same file extension for all the files.
Is anyone able to help with the terminal command I need to do this? So far I know that find . -type f will list all the files in the directory and subdirectories but Im unsure how to tell it to get them to move them all into another folder.
For anyone else who may have this same issue:
Ive managed to extract just the .jpg's and put them in the parent folder.
find . -type f -iname '*.jpg' -mindepth 2 -print0 | xargs -0 -I{} mv -n '{}' .
Not quite what I wanted - I was hoping to get every single file and put it into a completely different folder if possible but this has got me further than before.
Go inside the source parent directory and use:
find . -type f -exec mv "$PWD"/{} <destination directory> \;
If you want to move all the files to parent directory itself, use it as the destination directory.

Problems using find and cp to copy just .jpg files from a LOT of directories to one new path

I tried the search, but couldn't find the answer to my specific problem.
When I use,
find /recovered_files "*.jpg" -type f -exec cp {} /out \;
to copy all .jpg files from directories within the /recovered_files directory, the /out directory gets filled with every single file (jpg, txt, xml etc etc) from within the source directories.
Can anyone please explain wherein my stupidity lies, pleeeeeease???
Many thanks, Mark.
What you're doing at the moment is equivalent to calling cp /dir/dir/dir/file.jpg /out for each file, which will copy the file into /out. Thus, all of the files are being put into the same directory.
rsync allows filters to select only certain files to be copied. Change from and to to the appropriate directories in the following:
rsync -r from/* to --include=*.jpg --filter='-! */' --prune-empty-dirs
Credit to this post for this solution.
Edit: changed to rsync solution. Original as follows:
find from -name "*.jpg" -type f -exec mkdir -p to/$(dirname {}) \; -exec cp --parents {} to \;
You should replace from and to with the appropriate locations, and this form won't quite work if from begins with /. Just cd to / first if you need to. Also, you'll end up with all the files inside to underneath the entire directory structure of from, but you can just move them back out again.

Copy all files with a certain extension from all subdirectories

Under unix, I want to copy all files with a certain extension (all excel files) from all subdirectories to another directory. I have the following command:
cp --parents `find -name \*.xls*` /target_directory/
The problems with this command are:
It copies the directory structure as well, and I only want the files (so all files should end up in /target_directory/)
It does not copy files with spaces in the filenames (which are quite a few)
Any solutions for these problems?
--parents is copying the directory structure, so you should get rid of that.
The way you've written this, the find executes, and the output is put onto the command line such that cp can't distinguish between the spaces separating the filenames, and the spaces within the filename. It's better to do something like
$ find . -name \*.xls -exec cp {} newDir \;
in which cp is executed for each filename that find finds, and passed the filename correctly. Here's more info on this technique.
Instead of all the above, you could use zsh and simply type
$ cp **/*.xls target_directory
zsh can expand wildcards to include subdirectories and makes this sort of thing very easy.
From all of the above, I came up with this version.
This version also works for me in the mac recovery terminal.
find ./ -name '*.xsl' -exec cp -prv '{}' '/path/to/targetDir/' ';'
It will look in the current directory and recursively in all of the sub directories for files with the xsl extension. It will copy them all to the target directory.
cp flags are:
p - preserve attributes of the file
r - recursive
v - verbose (shows you whats
being copied)
I had a similar problem. I solved it using:
find dir_name '*.mp3' -exec cp -vuni '{}' "../dest_dir" ";"
The '{}' and ";" executes the copy on each file.
I also had to do this myself. I did it via the --parents argument for cp:
find SOURCEPATH -name filename*.txt -exec cp --parents {} DESTPATH \;
In 2022 the zsh solution also works in Linux Bash:
cp **/*.extension /dest/dir
works as expected.
find [SOURCEPATH] -type f -name '[PATTERN]' |
while read P; do cp --parents "$P" [DEST]; done
you may remove the --parents but there is a risk of collision if multiple files bear the same name.
On macOS Ventura 13.1, on zsh, I saw the following error when there were too many files to copy, saw the following error:
zsh: argument list too long: cp
Had to use find command along with cp to get the files copied to my destination:
find ./module/*/src -name \*.java -print | while read filelocation; do cp $filelocation mydestinationlocation; done

Resources