2

I have multiple subdirectories at multiple levels containing a file results.out

./dir1/results.out
./dir2/dir21/results.out
./dir3/dir31/dir311/results.out

Now I need to search for string1 in results.out and extract the directory path of those results.out that contain the string1, since I need to move these sub-directories to another location. For example, I can get the file path using the following code

for i in $(find . -type f -name "results.out);
do
grep -l "string1" $i
done

How to modify the above code to get only the directory path?

5

If you have GNU find, you can print the path using the %h format specifier

    %h     Leading directories of file's name (all but the last ele‐
           ment).  If the file name contains no slashes (since it is
           in  the  current  directory)  the %h specifier expands to
           ".".

So for example you could do

find . -name 'results.out' -exec grep -q 'string1' {} \; -printf '%h\n'
4

With zsh:

print -rl ./**/results.out(.e_'grep -q string $REPLY'_:h)

this searches recursively for regular files (.) named results.out, runs grep -q ... on each of them and if that evaluates true it prints only the head of the path (the path without the last element).


Another way with find and sh, using parameter expansion to extract the head:

find . -type f -name results.out -exec grep -q string {} \; \
-exec sh -c 'printf %s\\n "${0%/*}"' {} \;
1
for i in $(find . -type f -name "results.out);
do
grep -l "string1" $i ; exitcode=${?}
if [ ${exitcode} -eq 0 ]  # string1 is found in file $i
then
   path=${i%/*}
   echo ${path}
fi
done
1

On a GNU system:

 find . -depth -type f -name results.out -exec grep -lZ string1 {} + |
   xargs -r0 dirname -z |
   xargs -r0 mv -t /dest/dir

Or:

 find . -depth -type f -name results.out -exec grep -lZ string1 {} + |
   LC_ALL=C sed -z 's|/[^/]*$||' |
   xargs -r0 mv -t /dest/dir

The -depth is so that if both ./A/results.out and ./A/B/results.out match, ./A/B is moved to /dest/dir/B before ./A is moved to /dest/dir/A.

0

Assuming I understood correctly you want to do just that:

find . -type f -name "results.out" -exec grep -l "string1" {} \; | xargs dirname

First part gets matching filenames, then xargs passes those as an argument to dirname program which 'strips' filelame from path

  • 1
    Even though dirname is available in any recent distribution of Linux, it is still an external command and might not be available on minimally installed systems. Just a word of caution. – MelBurslan Jul 20 '16 at 22:49
  • Not all dirname implementations accept more than one argument. (POSIX dirname specification doesn't). As usual with xargs, you'll also have issues with file paths containing blanks or quotes or backslash. You may as well use \; instead of + here to avoid having to run one grep per file. – Stéphane Chazelas Aug 3 '16 at 11:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.