Using find and grep in a specifc file in a specific directory - linux

I have the following directory structure:
./A1
./A2
./A3
./B
./C
In each one of the A* directories I have:
./A*/logs
./A*/test
in the logs directory I have:
./log-jan-1
./log-jan-2
./log-feb-1
How do I grep for a string in all January logs in the A directories?
I tried this, but it did not find the string although it is present in the log files:
find . -type d -name 'A*' print | xargs -n1 -I PATH grep string - PATH/logs/log-jan*
What am I doing wrong?

Why don't you simply use
grep string ./A*/logs/log-jan*
?

If it is not a typo, you should use -print (or -print0) instead of print.
But as find + xargs + grep constructs are hard to debug, you should test in sequence :
find . -type d -name 'A*' -print
find . -type d -name 'A*' -print0 | xargs -0 -n1 -I PATH echo grep string - PATH
and finally :
find . -type d -name 'A*' -print0 | xargs -0 -n1 -I PATH grep string - PATH/logs/log-jan*
In you use case, -print and -print0 should give same results, but for having been burnt with -print, I always use -print0 before xargs

Related

I want to get an output of the find command in shell script

Am trying to write a script that finds the files that are older than 10 hours from the sub-directories that are in the "HS_client_list". And send the Output to a file "find.log".
#!/bin/bash
while IFS= read -r line; do
echo Executing cd /moveit/$line
cd /moveit/$line
#Find files less than 600 minutes old.
find $PWD -type f -iname "*.enc" -mmin +600 -execdir basename '{}' ';' | xargs ls > /home/infa91punv/find.log
done < HS_client_list
However, the script is able to cd to the folders from HS_client_list(this file contents the name of the subdirectories) but, the find command (find $PWD -type f -iname "*.enc" -mmin +600 -execdir basename '{}' ';' | xargs ls > /home/infa91punv/find.log) is not working. The Output file is empty. But when I run find $PWD -type f -iname "*.enc" -mmin +600 -execdir basename '{}' ';' | xargs ls > /home/infa91punv/find.log as a command it works and from the script it doesn't.
You are overwriting the file in each iteration.
You can use xargs to perform find on multiple directories; but you have to use an alternate delimiter to avoid having xargs populate the {} in the -execdir command.
sed 's%^%/moveit/%' HS_client_list |
xargs -I '<>' find '<>' -type f -iname "*.enc" -mmin +600 -execdir basename {} \; > /home/infa91punv/find.log
The xargs ls did not seem to perform any useful functionality, so I took it out. Generally, don't use ls in scripts.
With GNU find, you could avoid the call to an external utility, and use the -printf predicate to print just the part of the path name that you care about.
For added efficiency, you could invoke a shell to collect the arguments:
sed 's%^%/moveit/%' HS_client_list |
xargs sh -c 'find "$#" -type f -iname "*.enc" -mmin +600 -execdir basename {} \;' _ >/home/infa91punv/find.log
This will run as many directories as possible in a single find invocation.
If you want to keep your loop, the solution is to put the redirection after done. I would still factor out the cd, and take care to quote the variable interpolation.
while IFS= read -r line; do
find /moveit/"$line" -type f -iname "*.enc" -mmin +600 -execdir basename '{}' ';'
done < HS_client_list >/home/infa91punv/find.log

Print the directory where the 'find' linux command finds a match

I have a bunch of directories; some of them contain a '.todo' file.
/storage/BCC9F9D00663A8043F8D73369E920632/.todo
/storage/BAE9BBF30CCEF5210534E875FC80D37E/.todo
/storage/CBB46FF977EE166815A042F3DEEFB865/.todo
/storage/8ABCBF3194F5D7E97E83C4FD042AB8E7/.todo
/storage/9DB9411F403BD282B097CBF06A9687F5/.todo
/storage/99A9BA69543CD48BA4BD59594169BBAC/.todo
/storage/0B6FB65D4E46CBD8A9B1E704CFACC42E/.todo
I'd like the 'find' command to print me only the directory, like this
/storage/BCC9F9D00663A8043F8D73369E920632
/storage/BAE9BBF30CCEF5210534E875FC80D37E
/storage/CBB46FF977EE166815A042F3DEEFB865
...
here's what I have so far, but it lists the '.todo' file as well
#!/bin/bash
STORAGEFOLDER='/storage'
find $STORAGEFOLDER -name .todo -exec ls -l {} \;
Should be dumb stupid, but i'm giving up :(
To print the directory name only, use -printf '%h\n'. Also recommended to quote your variable with doublequotes.
find "$STORAGEFOLDER" -name .todo -printf '%h\n'
If you want to process the output:
find "$STORAGEFOLDER" -name .todo -printf '%h\n' | xargs ls -l
Or use a loop with process substitution to make use of a variable:
while read -r DIR; do
ls -l "$DIR"
done < <(exec find "$STORAGEFOLDER" -name .todo -printf '%h\n')
The loop would actually process one directory at a time whereas in xargs the directories are passed ls -l in one shot.
To make it sure that you only process one directory at a time, add uniq:
find "$STORAGEFOLDER" -name .todo -printf '%h\n' | uniq | xargs ls -l
Or
while read -r DIR; do
ls -l "$DIR"
done < <(exec find "$STORAGEFOLDER" -name .todo -printf '%h\n' | uniq)
If you don't have bash and that you don't mind about preserving changes to variables outside the loop you can just use a pipe:
find "$STORAGEFOLDER" -name .todo -printf '%h\n' | uniq | while read -r DIR; do
ls -l "$DIR"
done
The quick and easy answer for stripping off a file name and showing only the directory it’s in is dirname:
#!/bin/bash
STORAGEFOLDER='/storage'
find "$STORAGEFOLDER" -name .todo -exec dirname {} \;

Shell command for counting words in files

I want to run a command, that will count the number of words in all file. (From the selected number of files)
If i do like, find ABG-Development/ -name "*.php" | grep "<?" | wc -l , it will search only in the filename not the file contents.
And I tried one more way like
find ABG-Development/ -name "*.php" -exec grep "<?" {} \; | wc -l, I got error.
In above example I need how many time "
Please help..
use xargs
find ABG-Development/ -name "*.php" -print0 | xargs -0 grep "<?" | wc -l

How to only get file name with Linux 'find'?

I'm using find to all files in directory, so I get a list of paths. However, I need only file names. i.e. I get ./dir1/dir2/file.txt and I want to get file.txt
In GNU find you can use -printf parameter for that, e.g.:
find /dir1 -type f -printf "%f\n"
If your find doesn't have a -printf option you can also use basename:
find ./dir1 -type f -exec basename {} \;
Use -execdir which automatically holds the current file in {}, for example:
find . -type f -execdir echo '{}' ';'
You can also use $PWD instead of . (on some systems it won't produce an extra dot in the front).
If you still got an extra dot, alternatively you can run:
find . -type f -execdir basename '{}' ';'
-execdir utility [argument ...] ;
The -execdir primary is identical to the -exec primary with the exception that utility will be executed from the directory that holds the current file.
When used + instead of ;, then {} is replaced with as many pathnames as possible for each invocation of utility. In other words, it'll print all filenames in one line.
If you are using GNU find
find . -type f -printf "%f\n"
Or you can use a programming language such as Ruby(1.9+)
$ ruby -e 'Dir["**/*"].each{|x| puts File.basename(x)}'
If you fancy a bash (at least 4) solution
shopt -s globstar
for file in **; do echo ${file##*/}; done
If you want to run some action against the filename only, using basename can be tough.
For example this:
find ~/clang+llvm-3.3/bin/ -type f -exec echo basename {} \;
will just echo basename /my/found/path. Not what we want if we want to execute on the filename.
But you can then xargs the output. for example to kill the files in a dir based on names in another dir:
cd dirIwantToRMin;
find ~/clang+llvm-3.3/bin/ -type f -exec basename {} \; | xargs rm
On mac (BSD find) use:
find /dir1 -type f -exec basename {} \;
As others have pointed out, you can combine find and basename, but by default the basename program will only operate on one path at a time, so the executable will have to be launched once for each path (using either find ... -exec or find ... | xargs -n 1), which may potentially be slow.
If you use the -a option on basename, then it can accept multiple filenames in a single invocation, which means that you can then use xargs without the -n 1, to group the paths together into a far smaller number of invocations of basename, which should be more efficient.
Example:
find /dir1 -type f -print0 | xargs -0 basename -a
Here I've included the -print0 and -0 (which should be used together), in order to cope with any whitespace inside the names of files and directories.
Here is a timing comparison, between the xargs basename -a and xargs -n1 basename versions. (For sake of a like-with-like comparison, the timings reported here are after an initial dummy run, so that they are both done after the file metadata has already been copied to I/O cache.) I have piped the output to cksum in both cases, just to demonstrate that the output is independent of the method used.
$ time sh -c 'find /usr/lib -type f -print0 | xargs -0 basename -a | cksum'
2532163462 546663
real 0m0.063s
user 0m0.058s
sys 0m0.040s
$ time sh -c 'find /usr/lib -type f -print0 | xargs -0 -n 1 basename | cksum'
2532163462 546663
real 0m14.504s
user 0m12.474s
sys 0m3.109s
As you can see, it really is substantially faster to avoid launching basename every time.
Honestly basename and dirname solutions are easier, but you can also check this out :
find . -type f | grep -oP "[^/]*$"
or
find . -type f | rev | cut -d '/' -f1 | rev
or
find . -type f | sed "s/.*\///"
-exec and -execdir are slow, xargs is king.
$ alias f='time find /Applications -name "*.app" -type d -maxdepth 5'; \
f -exec basename {} \; | wc -l; \
f -execdir echo {} \; | wc -l; \
f -print0 | xargs -0 -n1 basename | wc -l; \
f -print0 | xargs -0 -n1 -P 8 basename | wc -l; \
f -print0 | xargs -0 basename | wc -l
139
0m01.17s real 0m00.20s user 0m00.93s system
139
0m01.16s real 0m00.20s user 0m00.92s system
139
0m01.05s real 0m00.17s user 0m00.85s system
139
0m00.93s real 0m00.17s user 0m00.85s system
139
0m00.88s real 0m00.12s user 0m00.75s system
xargs's parallelism also helps.
Funnily enough i cannot explain the last case of xargs without -n1.
It gives the correct result and it's the fastest ¯\_(ツ)_/¯
(basename takes only 1 path argument but xargs will send them all (actually 5000) without -n1. does not work on linux and openbsd, only macOS...)
Some bigger numbers from a linux system to see how -execdir helps, but still much slower than a parallel xargs:
$ alias f='time find /usr/ -maxdepth 5 -type d'
$ f -exec basename {} \; | wc -l; \
f -execdir echo {} \; | wc -l; \
f -print0 | xargs -0 -n1 basename | wc -l; \
f -print0 | xargs -0 -n1 -P 8 basename | wc -l
2358
3.63s real 0.10s user 0.41s system
2358
1.53s real 0.05s user 0.31s system
2358
1.30s real 0.03s user 0.21s system
2358
0.41s real 0.03s user 0.25s system
I've found a solution (on makandracards page), that gives just the newest file name:
ls -1tr * | tail -1
(thanks goes to Arne Hartherz)
I used it for cp:
cp $(ls -1tr * | tail -1) /tmp/

how to find files containing a string using egrep

I would like to find the files containing specific string under linux.
I tried something like but could not succeed:
find . -name *.txt | egrep mystring
Here you are sending the file names (output of the find command) as input to egrep; you actually want to run egrep on the contents of the files.
Here are a couple of alternatives:
find . -name "*.txt" -exec egrep mystring {} \;
or even better
find . -name "*.txt" -print0 | xargs -0 egrep mystring
Check the find command help to check what the single arguments do.
The first approach will spawn a new process for every file, while the second will pass more than one file as argument to egrep; the -print0 and -0 flags are needed to deal with potentially nasty file names (allowing to separate file names correctly even if a file name contains a space, for example).
try:
find . -name '*.txt' | xargs egrep mystring
There are two problems with your version:
Firstly, *.txt will first be expanded by the shell, giving you a listing of files in the current directory which end in .txt, so for instance, if you have the following:
[dsm#localhost:~]$ ls *.txt
test.txt
[dsm#localhost:~]$
your find command will turn into find . -name test.txt. Just try the following to illustrate:
[dsm#localhost:~]$ echo find . -name *.txt
find . -name test.txt
[dsm#localhost:~]$
Secondly, egrep does not take filenames from STDIN. To convert them to arguments you need to use xargs
find . -name *.txt | egrep mystring
That will not work as egrep will be searching for mystring within the output generated by find . -name *.txt which are just the path to *.txt files.
Instead, you can use xargs:
find . -name *.txt | xargs egrep mystring
You could use
find . -iname *.txt -exec egrep mystring \{\} \;
Here's an example that will return the file paths of a all *.log files that have a line that begins with ERROR:
find . -name "*.log" -exec egrep -l '^ERROR' {} \;
there's a recursive option from egrep you can use
egrep -R "pattern" *.log
If you only want the filenames:
find . -type f -name '*.txt' -exec egrep -l pattern {} \;
If you want filenames and matches:
find . -type f -name '*.txt' -exec egrep pattern {} /dev/null \;

Resources