Find files and print only their parent directories
Asked Answered
W

9

19

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
Watts answered 7/3, 2012 at 7:33 Comment(0)
H
20

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
Holdback answered 7/3, 2012 at 8:33 Comment(3)
And can i do ll to find the permsissions of the resulting directory find . -name '.user_repo.log' | xargs -i{} dirname {} | xargs -i{} dirname {} |xargs llWatts
Assuming the ll you refer to is the commonly used alias for "ls -lA", then no. xargs will only run commands, not aliases. However, you could do "xargs ls -lA" instead...Holdback
Be warned, -i flag for xargs is listed as deprecated, preferably use -I flag instead.Tiffany
H
13

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).

Hullabaloo answered 10/8, 2016 at 9:28 Comment(0)
F
3
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.

Folger answered 7/3, 2012 at 7:39 Comment(0)
P
1

@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
Pemba answered 7/3, 2012 at 8:13 Comment(2)
Any particular reason you're using a different file descriptor?Shaunna
In case you want to do something with stdin inside the loop. It's explained at the link above.Pemba
I
0

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|'
Ingrained answered 7/3, 2012 at 7:56 Comment(0)
Y
0
find -type f -exec bash -c 'echo "${1%/*}"' bash {} \;
Yowl answered 18/11, 2017 at 10:57 Comment(2)
Can you give a bit of explanation of this? I can confirm it works, but I'm particularly confused by the 1% and the final bash {}Pedo
gnu.org/software/bash/manual/html_node/…Yowl
L
0
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

Lucey answered 12/1, 2021 at 4:30 Comment(0)
R
0

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.

Respire answered 12/5, 2023 at 9:8 Comment(0)
M
-1
find /nfs/office -name '.user.log' | while read line
do
    echo $line | awk -F/ '{print $(NF-1),$NF}'
done
Medrek answered 7/3, 2012 at 7:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.