BitWrecker
BitWrecker

Reputation: 184

Find all directories that contain only hidden files and/or hidden directories

Issue

Visual Explanation

+--- Root_Dir
|   +--- Dir_A
|   |   +--- abc.txt
|   |   +--- 123.txt
|   |   +--- .hiddenfile
|   |   +--- .hidden_dir
|   |   |   +--- normal_sub_file_1.txt
|   |   |   +--- .hidden_sub_file_1.txt
|   |     
|   +--- Dir_B
|   |   +--- abc.txt
|   |   +--- .hidden_dir
|   |   |   +--- normal_sub_file_2.txt
|   |   |   +--- .hidden_sub_file_2.txt
|   |    
|   +--- Dir_C
|   |   +--- 123.txt
|   |   +--- program.c
|   |   +--- a.out
|   |   +--- .hiddenfile
|   |   
|   +--- Dir_D
|   |   +--- .hiddenfile
|   |   +--- .another_hiddenfile
|   |     
|   +--- Dir_E
|   |   +--- .hiddenfile
|   |   +--- .hidden_dir
|   |   |   +--- normal_sub_file_3.txt   # This is OK because its within a hidden directory, aka won't be checked
|   |   |   +--- .hidden_sub_file_3.txt
|   | 
|   +--- Dir_F
|   |   +--- .hidden_dir
|   |   |   +--- normal_sub_file_4.txt
|   |   |   +--- .hidden_sub_file_4.txt

Desired Output

Attempts

Upvotes: 1

Views: 472

Answers (3)

oguz ismail
oguz ismail

Reputation: 50750

Parsing find's output is not a good idea; -exec exists, and sh can do the filtering without breaking anything.

find . -type d -exec sh -c '
for d; do
  for f in "$d"/*; do
    test -e "$f" &&
      continue 2
  done
  for f in "$d"/.[!.]* "$d"/..?*; do
    if test -e "$f"; then
      printf %s\\n "$d"
      break
    fi
  done
done' sh {} +

You can adjust the depth using whatever extension your find provides for it.

Upvotes: 1

konsolebox
konsolebox

Reputation: 75488

This will work if your filenames contain no newlines.

find -name '.*' | awk -F/ -v OFS=/ '{ --NF } !a[$0]++'

Learn awk: https://www.gnu.org/software/gawk/manual/gawk.html

Upvotes: 0

Paul Hodges
Paul Hodges

Reputation: 15293

Assuming this structure, you don't need find.
Adjust your pattern as needed.

for d in $ROOT_DIR/Dir_?/; do
  lst=( $d* );  [[ -e "${lst[0]}" ]] && continue # normal files, skip
  lst=( $d.* ); [[ -e "${lst[2]}" ]] || continue # NO hidden, so skip
  echo "$d"
done

I rebuilt your file structure in my /tmp dir and saved this as tst, so

$: ROOT_DIR=/tmp ./tst
/tmp/Dir_D/
/tmp/Dir_E/
/tmp/Dir_F/

Note that the confirmation of hidden files uses "${lst[2]}" because the first 2 will always be . and .., which don't count.

You could probably use for d in $ROOT_DIR/*/.
I suspect this'll do for you. (mindepth=2, maxdepth=2)

If you needed deeper subdirectories (mindepth=3, maxdepth=3) you could add a level -

for d in $ROOT_DIR/*/*/

and/or both (mindepth=2, maxdepth=3)

for d in $ROOT_DIR/*/ $ROOT_DIR/*/*/

or if you didn't want a mindepth/maxdepth,

shopt -s globstar
for d in $ROOT_DIR/**/

Upvotes: 1

Related Questions