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

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 {} \;

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

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

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

Linux find files and grep then list by date

I'm trying to find files with the name of formClass.php that contain a string of checkCookie and I want to list the files by date showing the date, size, owner and the group. The files are in my home directory.
I have this working, but it doesn't show date, owner, etc...
find /home -name formClass.php -exec grep -l "checkCookie" {} \;
I was thinking that I could add "trhg" to the list like this, but it didn't work:
find /home -name formClass.php -exec grep -ltrhg "checkCookie" {} \;
Thanks
Try find /home -name formClass.php -print0 | xargs -0 grep -l --null checkCookie | xargs -0 ls -l
ls -lt `grep -lr --include=formClass.php "checkCookie" *`
Take a look at man ls for another sorting options.
It seems there are a few ways to do this. I found this and it worked for me.
find /home -name formClass.php -exec grep -l "checkCookie" {} \; | xargs ls -ltrhg
thank you for the other suggestions.

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/

dos2unix command

I have this script
#!/bin/sh
for i in `ls -R`
do
echo "Changing $i"
fromdos $i
done
I want to remove "^M" charcaters from many files which are in more subdirectories. I got this:
fromdos: Unable to access file
Is there somethig i'm missing?
Thanks in advance.
ls -R lists everything, including directories. So you're telling fromdos to act on actual directories is some cases.
Try something like this:
find . -type f -exec fromdos {} \;
I guess you don't need a for loop.
Here is a quick panorama of solutions for files with extension ".ext" (such commands shall be somehow restrictive)
note : ^M is obtained with CTRL-V" + "CTRL-M"
# PORTABLE SOLUTION
find /home -type f -name "*.ext" -exec sed -i -e 's/^M$//' {} \;
# GNU-sed
find /home -type f -name "*.ext" -exec sed -i -e "s/\x0D$//g" {} \;
# SED with more recent nux
find /home -type f -name "*.ext" -exec sed -i -e "s/\r$//g" {} \;
# DOS2UNIX
find /home -type f -name "*.ext" -print0 | while read -r -d "$(printf "\000")" -r path; do dos2unix $path $path"_new"; done
# AWK
find /home -type f -name "*.ext" -print0 | while read -r -d "$(printf "\000")" -r path; do awk '{ sub("\r$", ""); print }' $path > $path"_new"; done
# TR
find /home -type f -name "*.ext" -print0 | while read -r -d "$(printf "\000")" -r path; do cat $path | tr -d '\r' > $path"_new"; done
# PERL
find /home -type f -name "*.ext" -exec perl -pi -e 's/\r//g' {} \;

Resources