find recursively, but with specific sub-folder name - linux

This command find all files name "log_7" recursively in current folder.
find . -name log_7
Assume many sub-folders under the current folder tree has a file with that same name "log_7":
./am/f1/log_7
./ke/f2/log_7
./sa/f6/log_7
..
./xx/f97/log_7
Is there a way to explicitly say that we only want to search for "log_7" in a folder name "f2" ? such that the result from find will only list only one entry:
./ke/f2/log_7

You could use a regular expression.
find . -regex '.*/f2/log_7'
This will only match if log_7 is directly nested under f2

There is a different way is to use xargs for same thing
find . -name filename | xargs grep -e refectory
But find with built in regex is preferable.

Simple glob should do:
printf '%s\n' */f2/log_7
If there is a possibility for more leading folders, you can use the globstar option:
shopt -s globstar
printf '%s\n' **/f2/log_7

Related

Find files recursively and rename based on their full path

I'm looking to search for files of a specific name, modify the name to the full path and then copy the results to another folder.
Is it possible to update each find result with the full path as the file name; i.e.
./folder/subfolder/my-file.csv
becomes
folder_subfolder_my-file.csv
I am listing the files using the following and would like to script it.
find . -name my-file.csv -exec ls {} \;
Since you're using bash, you can take advantage of globstar and use a for loop:
shopt -s globstar # set globstar option
for csv in **/my-file.csv; do
echo "$csv" "${csv//\//_}"
done
shopt -u globstar # unset the option if you don't want it any more
With globstar enabled, ** does a recursive search (similar to the basic functionality of find).
"${csv//\//_}" is an example of ${var//match/replace}, which does a global replacement of all instances of match (here an escaped /) with replace.
If you're happy with the output, then change the echo to mv.
Just to demonstrate how to do this with find;
find . -type f -exec bash -c '
for file; do
f=${file#./}
cp "$file" "./${f//\//_}"
done' _ {} +
The Bash pattern expansion ${f//x/y} replaces x with y throughout. Because find prefixes each found file with the path where it was found (here, ./) we trim that off in order to avoid doing mv "./file" "._file". And because the slash is used in the parameter expansion itself, we need to backslash the slash we want the shell to interpret literally. Finally, because this parameter expansion syntax is a Bash-only extension, we use bash rather than sh.
Obviously, if you want to rename rather than copy, replace cp with mv.
If your find does not support -exec ... + this needs to be refactored somewhat (probably to use xargs); but it should be supported on any reasonably modern platform.
With perl's rename command ...
$ prename
Usage: rename [-v] [-n] [-f] perlexpr [filenames]
... you can rename multiple files by applying a regular expression. rename also accepts file names via stdin:
find ... | rename -n 's#/#_#g'
Check the results and if they are fine, remove -n.

Replace underscore with minus recursively but only for certain extensions?

I want to replace all underscores with a minus for files in a folder and its subfolders and I want to include only jpg, JPG and png. For a start I tried rename '/_/-/g' ./* but that does not include subfolders. How would I do that?
Thanks
Globstar allows you to match files anywhere in a folder or under it. ${f//_/-} is a bash "Pattern substitution": it replaces the value of $f, the double slashes before the pattern mean replace all matches.
shopt -s globstar
for f in **/*.jpg **/*.JPG **/*.png
do
mv "$f" "${f//_/-}"
done
My system does not support this syntax of rename, so I can't give you good advice in its use, but you might also be able to use globstar to generate filenames for it.
On Linux, you can traverse the folders with "find":
find . -name "*.pdf" -type f -exec #rename command here, current filename is "{}"# \;
Edit: Forgot the \;

Using "grep" to search for specific type of files in all subdirectories

I am trying to find a specific line in files that contains "Mutual_Values_23.0" in a directory that contains a lot of subdirectories. I know this line number is stored in a file which starts with "gnuout_mutual_....txt" (the ellipses part of the file name is the time stamp so that varies).
I wanted to know if there is a way to specify "grep" command to look into the subdirectories only for the files starting with "gnuout_mutual_....txt"
I have tried
grep -r "Mutual_Values_23.0" *
but that's taking a long time
You can use the following option of grep:
--include=GLOB
Search only files whose base name matches GLOB (using wildcard matching as described under --exclude).
And for the line number you should use the -n option.
From within the root of the folders you want to look into, you can use this final command:
grep -nr "Mutual_Values_23.0" --include="gnuout_mutual_*txt"
Use find to search all sub-directories for the "gnuout...txt` file with the search string "Mutual_Values_23.0"
find . -mindepth 1 -name gnuout_mutual_\*.txt -type f -exec grep "Mutual_Values_23.0" {} +
If you make use of bash, you can use the globstar option:
globstar
If set, the pattern ** used in a pathname expansion context will
match all files and zero or more directories and subdirectories.
If the pattern is followed by a /, only directories and
subdirectories match.
So you can use it like:
$ shopt -s globstar
$ grep "search_string" **/glob-pattern
or in the case of the OP:
$ shopt -s globstar
$ grep Mutual_Values_23.0 **/gnuout_mutual_*.txt
GNU grep has the --include GLOB option where GLOB can be used to specify the file name pattern that you need to match.
grep -rn --include 'gnuout_mutual_*txt' 'Mutual_Values_23.0' .
You could use find to search for files and pass results to grep.
find /directory_where_to_search/ -iname 'gnuout_mutual_*.txt' | xargs grep 'Mutual_Values_23.0' -sl
Use this command:
$ find . -name "*Mutual_Values_23.0*"
Note: Run this command in the directory where you want to search your set of files.
Hope it helps, cheers!

How to recursively delete all files in folder that dont match a given pattern

I would like to delete all files in a given folder that dont match the pattern ^transactions_[0-9]+
Let's say I have these files in the folder
file_list
transactions_010116.csv
transactions_020116.csv
transactions_check_010116.csv
transactions_check_020116.csv
I would like to delete transactions_check_010116.csv and transactions_check_020116.csv and leave the first two as they are using ^transactions_[0-9]+
I've been trying to use find something like below, but this expression deletes everything in the folder not just the files that dont match the pattern:
find /my_file_location -type f ! -regex '^transactions_[0-9]+' -delete
What i'm trying to do here is using regex find all files in folder that dont start with ^transactions_[0-9]+ and delete them.
Depending on your implementation, you could have to use option -E to allow the use of full regexes. An other problem is that -regex gives you an almost full path starting with the directory you passed.
So the correct command should be:
find -E /my_file_location ! -regex '.*/transactions_[0-9]+$' -type f -delete
But you should first issue the same with -print to be sure...
grep has -v option to grep everything not matching the provided regex:
find . | grep -v '^transactions_[0-9]+' | xargs rm -f

Add prefix to all images (recursive)

I have a folder with more than 5000 images, all with JPG extension.
What i want to do, is to add recursively the "thumb_" prefix to all images.
I found a similar question: Rename Files and Directories (Add Prefix) but i only want to add the prefix to files with the JPG extension.
One of possibly solutions:
find . -name '*.jpg' -printf "'%p' '%h/thumb_%f'\n" | xargs -n2 echo mv
Principe: find all needed files, and prepare arguments for the standard mv command.
Notes:
arguments for the mv are surrounded by ' for allowing spaces in filenames.
The drawback is: this will not works with filenames what are containing ' apostrophe itself, like many mp3 files. If you need moving more strange filenames check bellow.
the above command is for dry run (only shows the mv commands with args). For real work remove the echo pretending mv.
ANY filename renaming. In the shell you need a delimiter. The problem is, than the filename (stored in a shell variable) usually can contain the delimiter itself, so:
mv $file $newfile #will fail, if the filename contains space, TAB or newline
mv "$file" "$newfile" #will fail, if the any of the filenames contains "
the correct solution are either:
prepare a filename with a proper escaping
use a scripting language what easuly understands ANY filename
Preparing the correct escaping in bash is possible with it's internal printf and %q formatting directive = print quoted. But this solution is long and boring.
IMHO, the easiest way is using perl and zero padded print0, like next.
find . -name \*.jpg -print0 | perl -MFile::Basename -0nle 'rename $_, dirname($_)."/thumb_".basename($_)'
The above using perl's power to mungle the filenames and finally renames the files.
Beware of filenames with spaces in (the for ... in ... expression trips over those), and be aware that the result of a find . ... will always start with ./ (and hence try to give you names like thumb_./file.JPG which isn't quite correct).
This is therefore not a trivial thing to get right under all circumstances. The expression I've found to work correctly (with spaces, subdirs and all that) is:
find . -iname \*.JPG -exec bash -c 'mv "$1" "`echo $1 | sed \"s/\(.*\)\//\1\/thumb/\"`"' -- '{}' \;
Even that can fall foul of certain names (with quotes in) ...
In OS X 10.8.5, find does not have the -printf option. The port that contained rename seemed to depend upon a WebkitGTK development package that was taking hours to install.
This one line, recursive file rename script worked for me:
find . -iname "*.jpg" -print | while read name; do cur_dir=$(dirname "$name"); cur_file=$(basename "$name"); mv "$name" "$cur_dir/thumb_$cur_file"; done
I was actually renaming CakePHP view files with an 'admin_' prefix, to move them all to an admin section.
You can use that same answer, just use *.jpg, instead of just *.
for file in *.JPG; do mv $file thumb_$file; done
if it's multiple directory levels under the current one:
for file in $(find . -name '*.JPG'); do mv $file $(dirname $file)/thumb_$(basename $file); done
proof:
jcomeau#intrepid:/tmp$ mkdir test test/a test/a/b test/a/b/c
jcomeau#intrepid:/tmp$ touch test/a/A.JPG test/a/b/B.JPG test/a/b/c/C.JPG
jcomeau#intrepid:/tmp$ cd test
jcomeau#intrepid:/tmp/test$ for file in $(find . -name '*.JPG'); do mv $file $(dirname $file)/thumb_$(basename $file); done
jcomeau#intrepid:/tmp/test$ find .
.
./a
./a/b
./a/b/thumb_B.JPG
./a/b/c
./a/b/c/thumb_C.JPG
./a/thumb_A.JPG
jcomeau#intrepid:/tmp/test$
Use rename for this:
rename 's/(\w{1})\.JPG$/thumb_$1\.JPG/' `find . -type f -name *.JPG`
For only jpg files in current folder
for f in `ls *.jpg` ; do mv "$f" "PRE_$f" ; done

Resources