19

I'm trying to get a list of directories that are contained within a specific folder.

Given these example folders:

foo/bar/test
foo/bar/test/css
foo/bar/wp-content/plugins/XYZ
foo/bar/wp-content/plugins/XYZ/js
foo/bar/wp-content/plugins/XYZ/css
baz/wp-content/plugins/ABC
baz/wp-content/plugins/ABC/inc
baz/wp-content/plugins/ABC/inc/lib
baz/wp-content/plugins/DEF
bat/bar/foo/blog/wp-content/plugins/GHI

I'd like a command that will return:

XYZ
ABC
DEF
GHI

Essentially, I'm looking for the folders that are inside of wp-content/plugins/

Using find has gotten me the closest, but I can't use -maxdepth, because the folder is variably away from where I'm searching.

Running the following returns all of the child directories, recursively.

find -type d -path *wp-content/plugins/*

foo/bar/wp-content/plugins/XYZ
foo/bar/wp-content/plugins/XYZ/js
foo/bar/wp-content/plugins/XYZ/css
baz/wp-content/plugins/ABC
baz/wp-content/plugins/ABC/inc
baz/wp-content/plugins/ABC/inc/lib
baz/wp-content/plugins/DEF
bat/bar/foo/blog/wp-content/plugins/GHI

7 Answers 7

19

Just add a -prune so that the found directories are not descended into:

find . -type d -path '*/wp-content/plugins/*' -prune -print

You need to quote that *wp-content/plugins/* as it's also a shell glob.

If you want only the directory names as opposed to their full path, with GNU find, you can replace the -print with -printf '%f\n' or assuming the file paths don't contain newline characters, pipe the output of the above command to awk -F / '{print $NF}' or sed 's|.*/||' (also assuming the file paths contain only valid characters).

With zsh:

printf '%s\n' **/wp-content/plugins/*(D/:t)

**/ is any level of subdirectories (feature originating in zsh in the early nighties, and now found in most other shells like ksh93, tcsh, fish, bash, yash though generally under some option), (/) to select only files of type directory, D to include hidden (dot) ones, :t to get the tail (file name).

7
  • This find will convert newlines in dirs to a ?. For bash, (as tagged in the question) this works.
    – user232326
    Mar 1, 2017 at 2:42
  • @sorontar, find implementations that output a ? or any replacement character or any form of escaping for non-printable characters only do that when the output goes to a terminal, not when it goes to a pipe like here. Mar 1, 2017 at 8:28
  • The first (and only non zsh) command that is in your answer has no pipes, it is the one I tested, and is the one that change newlines. When piped, as your said: "assuming the file paths don't contain newline characters", so, you already admitted that it also fails with newlines.
    – user232326
    Mar 1, 2017 at 9:13
  • @sorontar, sorry I thought you were commenting on the second part to say that it would work OK as the newlines would be escaped so would not break the assumption we're making for sed/awk that all files are on a line. Now, One may argue that getting a ABC?BCD or ABC\nBCD for interactive/terminal output would be more useful than getting two ABC and BCD lines for a dir called $'ABC\nBCD. That's at the level of user presentation, we could also point out that for a file called $'AB\bC', when the output goes to a terminal, you'd see AC without such a ? replacement... Mar 1, 2017 at 10:03
  • Find with -prune and using printf to modify the output format gave me exactly what I was asking for. Perfect--thanks!
    – Nick
    Mar 3, 2017 at 17:55
7

You could have find recurse, sort of:

find / -type d -path *wp-content/plugins -exec find {} -maxdepth 1 -mindepth 1 -type d \;
4

Bash-ically:

shopt -s globstar
printf "%s\n" **/wp-content/plugins/*

prints:

bat/bar/foo/blog/wp-content/plugins/GHI
baz/wp-content/plugins/ABC
baz/wp-content/plugins/DEF
foo/bar/wp-content/plugins/XYZ

or

shopt -s globstar
for d in **/wp-content/plugins/*; do printf "%s\n" ${d##*/}; done

prints:

GHI
ABC
DEF
XYZ
2
  • Nope, that will print files inside the dirs as well. Please read my answer.
    – user232326
    Feb 28, 2017 at 13:12
  • Good point; thank you! I had recreated their directory structure, but not placed test files in them.
    – Jeff Schaller
    Feb 28, 2017 at 13:50
3

For Bash: Simple (works for files/dirs with spaces and newlines):

shopt -s globstar                    # allow ** for several dirs.
a=(**/wp-content/plugins/*/)         # capture only wanted dirs.
a=("${a[@]%/}")                      # remove trailing slash (dirs) `/`.
printf '%s\n' "${a[@]##*/}"          # print only the last dir name.

Wll print:

GHI
ABC
DEF
XYZ

Even if there are files created.

3
  • Note that it doesn't look inside hidden dirs, skips hidden files (see the dotglob option if that's unwanted) and includes symlinks to directories in addition to directories (** would also traverse symlinks in bash 4.2 and earlier). Mar 1, 2017 at 8:30
  • @StéphaneChazelas hidden files? The question asks for none, only dirs. Yes, hidden dirs could be independently selected (as a feature, if desired). And yes, symlinks are not transverse in the current version of bash.
    – user232326
    Mar 1, 2017 at 8:53
  • 1
    please don't regard my comments as aggressions. I upvoted your answer and added a note underneath to point out some facts about it that are not necessarily obvious. find doesn't exclude any file. globs excludes dot files, it's worth pointing out as a difference between the two. Mar 1, 2017 at 9:58
3

The tree command is designed exactly for this purpose. The depth can be controlled with the -L flag. Here is an example on a local Wordpress site that I maintain:

$ tree -L 1 wp-content/
wp-content/
├── index.php
├── plugins
└── themes

2 directories, 1 file

$ tree -L 2 wp-content/
wp-content/
├── index.php
├── plugins
│   ├── akismet
│   ├── contact-form-7
│   ├── index.php
│   └── wordpress-seo
└── themes
    ├── index.php
    ├── twentyfifteen
    └── twentysixteen

11 directories, 3 files

$ tree -L 1 wp-content/plugins/
wp-content/plugins/
├── akismet
├── contact-form-7
├── index.php
└── wordpress-seo

5 directories, 1 file
1

Building on DopeGhoti's answer, how about loop of matching:

find / -type d -iregex '.*/wp-content/plugins' -print0 | while read -r -d $'\0' D; do
    find "$D" -maxdepth 2 -mindepth 1
done

Reason for doing it this way is that you may find it confusing/cumbersome with multiple -exec's, whilst still avoiding issues with peculiar file names containing \n ' ', etc. -print0 will use null separators between results.

0

Building on what you started:

find -type d -path *wp-content/plugins/* | egrep -o "wp-content/plugins/[^/]+($|/)" | sed -r "s~wp-content/plugins/([^/]+)($|/)~\1~" | uniq  

egrep grabs *wp-content/plugins/* and any characters up to the next slash / or end of line $ - which includes the portion you want.

sed, with a ~ delimiter, selects the portion you want using the first set of parenthesis () and uses \1 (what you want) as the replacement of everything

uniq filters out duplicate results

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .