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

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.

Related

removing directory and sub directory which is not present in the list

This is my directory structure
find -type f -print0 | xargs -0 ls -t
./lisst.txt ./SAMN03272855/SRR1734376/SRR1734376_1.fastq.gz
./SAMN03272854/SRR1734375/SRR1734375_2.fastq.gz ./SAMN07605670/SRR6006890/SRR6006890_2.fastq.gz
./SAMN03272854/SRR1734375/SRR1734375_1.fastq.gz ./SAMN07605670/SRR6006890/SRR6006890_1.fastq.gz
./SAMN03272855/SRR1734376/SRR1734376_2.fastq.gz
So this is a small subset of my folder/files where i have around 70.
I have a made a list of files which i want to keep and other i would like to delete.
My list.txt contains SAMN03272854,SAMN03272855 but I want to remove SAMN07605670.
I ran this
find . ! -name 'lisst.txt' -type d -exec rm -vrf {} +
It removed everything
QUESTION UPDATE
In my list it contains the folder i want to keep and the one which are not there are to be removed.
The folders which are to be removed also contains subdirectories and files. I want to remove everything
Your command selects each directory in the tree, except a directories of the funny name lisst.txt. Once it finds a directory, you do a recursive remove of this directory. No surprise that your files are gone.
You can't use rm -r when you want to spare certain files from deletion. This means that you also can't remove a directory, which somewhere below in its subtree has a file you want to keep.
I would run two find commands: The first removes all the files, ignoring directories, and second one removes all directories, which are empty (bottom-up). Assuming that SAMN03272854 is indeed a file (as you told us in your question), this would be:
find . -type f \( ! \( -name SAMN03272854 -o -name SAMN03272855 \) \) -exec rm {}
find . -depth -type d -exec rmdir {} 2>/dev/null
The error redirection in the latter command suppresses messages from rmdir for directories which still contain files you want to keep. Of course other messages are also suppressed. I would during debugging run the command without error redirection, to see whether it is basically correct.
Things would get more complicated, if you have files and directories to keep, because to keep a directory likely implies to keep all the files below it. In this case, you can use the -prune option of find, which excludes directories including their subdirectories from being processed. See the find man page, which gives examples for this.

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 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.

Exclude specified directory when using `find` command

I have a directory which contains a number of files (no subdirectories). I wish to find these files. The following gets me close:
$ find docs
docs
docs/bar.txt
docs/baz.txt
docs/foo.txt
I don't want the directory itself to be listed. I could do this instead:
$ find docs -type f
docs/bar.txt
docs/baz.txt
docs/foo.txt
Using a wildcard seems to do the trick as well:
$ find docs/*
docs/bar.txt
docs/baz.txt
docs/foo.txt
My understanding is that these work in different ways: with -type, we're providing a single path to find, whereas in the latter case we're using wildcard expansion to pass several paths to find. Is there a reason to favour one approach over the other?
You have a UNIX tag, and you example has a *. Some versions of find have a problem with that.
If the directory has no subdirectories.
FYI.
Generally the first parms to find has to be a directory or a list of directories
find /dir1 /dir2 -print
Find is recursive - so it will follow each directory down listing every thing, symlinks, directories, pipes, and regular files. This can be confusing. -type delimits your search
find /dir1 /dir2 -type f -print
You can also have find do extra output example: have it rm files older than 30 days for example:
find /dir1 /dir2 -type f -mtime +30 -exec rm {} \;
Or give complete infomation
find /dir1 /dir2 -type f -mtime +30 -exec ls -l {} \;
find /dir1 /dir2 -type f -mtime +30 -ls # works on some systems
To answer your question: because find can be dangerous ALWAYS fully specify each directory , file type ,etc., when you are using a nasty command like rm. You might have forgotten your favorite directory is also in there. Or the one used to generate your paycheck. Using a wildcard is ok for just looking around.
Using *
find /path/to/files -type f -name 'foo*'
-- tics or quotes around strings with a star in them in some UNIX systems.
find docs -type f
will get you a listing of every non-directory file of every subdirectory of docs
find docs/*
will get you a listing of every file AND every subdirectory of docs

Find files, rename in place unix 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' \;

Resources