Rajeev
Rajeev

Reputation: 46919

Find files and print only their parent directories

I have the following commands. Wherever the .user.log file is present, we need to print the parent directories (i.e hht and wee1.) How can this be done?

$ cd /nfs//office/ && find . -name '.user.log'
./hht/info/.user.log
./wee1/info/.user.log

Upvotes: 19

Views: 28590

Answers (9)

TransferOrbit
TransferOrbit

Reputation: 227

Here’s a one-liner using awk. This is useful if you don’t have access to printf (eg. on macOS) or wish to have greater and more easily parsable control over which parts of the path you wish to extract.

find . -iregex ".*.user.log" | awk 'BEGIN{FS="/"; OFS="/"} {$NF=""; print $0}' | uniq

  • The find command with a proper regex will already find what you’re looking for.
  • awk then creates fields for each find result using / as field separator FS (and replacing them afterwards with OFS) and prints the entire line (print $0) except for the final field ($NF=“") which is the filename you may not want to see (eg. .user.log).
  • uniq simply ensures you don’t get multiple copies of the same path if several copies of the file pattern appear in a single directory.

If you don’t want the entire paths but only parts thereof, change print $0 to print $4 for example.

Upvotes: 0

STAR CP
STAR CP

Reputation: 1

for i in $(find . -name ".user.log" -exec readlink -f {} \;)
do 
     parent=$(dirname $i)
     echo $parent
done

this may work and produces output like

/nfs/office/hht/info

/nfs/office/wee1/info

Upvotes: 0

A T
A T

Reputation: 13826

find -type f -exec bash -c 'echo "${1%/*}"' bash {} \;

Upvotes: 0

rosencreuz
rosencreuz

Reputation: 1297

You can do this easily with the formatting options of the -printf action of find (see man find).

cd /nfs//office/ && find . -name '.user.log' -printf "%h\n"
./hht/info
./wee1/info

From the man page:

Screenshot of find manpage

%h\n will print path for each file on a new line.

Please note that -printf is GNU-only. It won't work on macOS (a BSD system).

Upvotes: 13

Adam
Adam

Reputation: 36703

Am I missing something here. Surely all this regex and/or looping is not necessary, a one-liner will do the job. Also "for foo in $()" solutions will fail when there are spaces in the path names.

Just use dirname twice with xargs, to get parent's parent...

# make test case
mkdir -p /nfs/office/hht/info
mkdir -p /nfs/office/wee1/info
touch /nfs/office/hht/info/.user.log
touch /nfs/office/wee1/info/.user.log

# parent's parent approach
cd /nfs//office/ && find . -name '.user.log' | xargs -I{} dirname {} | xargs -I{} dirname {}

# alternative, have find print parent directory, so dirname only needed once...
cd /nfs//office/ && find . -name ".user.log" -printf "%h\n"  | xargs -I{} dirname {}

Produces

./hht
./wee1

Upvotes: 20

l0b0
l0b0

Reputation: 58798

@trojanfoe has the right idea; this is just a way to get it to work safely with any filename, and pretty much any command within the loop:

while IFS= read -r -d '' -u 9
do
    echo "$(dirname -- "$(dirname -- "$REPLY")")"
done 9< <( find "/nfs/office/" -name '.user.log' -print0 )

If you want it to echo only the unique names:

while IFS= read -r -d '' -u 9
do
    echo "$(dirname -- "$(dirname -- "$REPLY")")"
done 9< <( find "/nfs/office/" -name '.user.log' -print0 ) | sort -u

Upvotes: 1

jcollado
jcollado

Reputation: 40374

You could do something like this:

cd /nfs/office/ && find . -name '.user.log' | xargs -n1 -I{} expr match {} '\(\.\(\/[^/]*\/\)\?\)'

where the xargs uses expr match to extract the part that starts with . until the first match of directory between slash characters (/dir/).

An alternative version using sed would be as follows:

cd /nfs/office/ &&  find . -name 'file.txt' | sed -r 's|(\./([^/]*/)?).*|\1|'

Upvotes: 0

xda1001
xda1001

Reputation: 2459

find /nfs/office -name '.user.log' | while read line
do
    echo $line | awk -F/ '{print $(NF-1),$NF}'
done

Upvotes: -1

trojanfoe
trojanfoe

Reputation: 122391

for file in $(find /nfs/office -name .user.log -print)
do
    parent=$(dirname $(dirname $file))
    echo $parent
done

EDIT: Sorry missed that you want the grandparent directory.

Upvotes: 3

Related Questions