108

Festival stores voicepack data in the following example directory structure:

/usr/share/festival/voices/<language>/<voicepack name>

What is the simplest one-liner (preferably using ls) to print out just the <voicepack name>'s, in all the potentially numerous <language> subdirectories?

6 Answers 6

131

I'm on Fedora, and these voicepacks are in a slightly different location:

$ ls /usr/share/festival/lib/voices/*/ -1 | grep -vE "/usr|^$"
kal_diphone
ked_diphone
nitech_us_awb_arctic_hts
nitech_us_bdl_arctic_hts
nitech_us_clb_arctic_hts
nitech_us_jmk_arctic_hts
nitech_us_rms_arctic_hts
nitech_us_slt_arctic_hts

You can just modify this like so:

$ ls /usr/share/festival/voices/*/ -1 | grep -vE "/usr|^$"

Using find

Using ls in this manor is typically frowned upon because the output of ls is difficult to parse. Better to use the find command, like so:

$ find /usr/share/festival/lib/voices -maxdepth 2 -mindepth 2 \
    -type d -exec basename {} \;
nitech_us_awb_arctic_hts
nitech_us_bdl_arctic_hts
nitech_us_slt_arctic_hts
nitech_us_jmk_arctic_hts
nitech_us_clb_arctic_hts
nitech_us_rms_arctic_hts
ked_diphone
kal_diphone

Details of find & basename

This command works by producing a list of full paths to files that are exactly 2 levels deep with respect to this directory:

/usr/share/festival/lib/voices

This list looks like this:

$ find /usr/share/festival/lib/voices -maxdepth 2 -mindepth 2 
/usr/share/festival/lib/voices/us/nitech_us_awb_arctic_hts
/usr/share/festival/lib/voices/us/nitech_us_bdl_arctic_hts
/usr/share/festival/lib/voices/us/nitech_us_slt_arctic_hts
/usr/share/festival/lib/voices/us/nitech_us_jmk_arctic_hts
/usr/share/festival/lib/voices/us/nitech_us_clb_arctic_hts
/usr/share/festival/lib/voices/us/nitech_us_rms_arctic_hts
/usr/share/festival/lib/voices/english/ked_diphone
/usr/share/festival/lib/voices/english/kal_diphon

But we want the last part of these directories, the leaf node. So we can make use of basename to parse it out:

$ basename /usr/share/festival/lib/voices/us/nitech_us_awb_arctic_hts
nitech_us_awb_arctic_hts

Putting it all together, we can make the find command pass each 2 level deep directory to the basename command. The notation basename {} is what is doing these basename conversions. Find calls it via it's -exec switch.

5
  • lol, pretty much exactly the same answer, great minds and all that :).
    – terdon
    Oct 2, 2013 at 18:43
  • +1 - For those who get tripped up when figuring out what -exec basename {} does, could you explain here?
    – user66001
    Oct 2, 2013 at 19:03
  • @user66001 - let me know if that explains it enough.
    – slm
    Oct 2, 2013 at 19:20
  • @user66001 - you can accept one of the answers if it solves your problem to your satisfication 8-)
    – slm
    Oct 2, 2013 at 19:32
  • 1
    The find command is what I need 99% of the time. Limit both max and min was key - I only did max. Example: find ~/ -maxdepth 1 -mindepth 1 -type d | xargs du -csh | sort -h Finds the biggest directories sorted on size
    – oligofren
    Apr 25, 2018 at 8:23
44

The easiest is

ls -d /usr/share/festival/voices/*/*

That is expanded by the shell into all sub directories of /usr/share/festival/voices/ and then to the contents of each of those sub directories.

If you only want to descend to a specific level as your title suggests, with some implementations of find like GNU's and some BSD's:

find /usr/share/festival/voices/ -mindepth 2 -maxdepth 3 -type d

That will find all directories (-type d) that are in a subdirectory of /usr/share/festival/voices/ because of mindepth 2 but are not deeper than 3 levels down (maxdepth 3). From man find:

   -maxdepth levels
          Descend at most levels (a non-negative integer) levels of direc‐
          tories below the command line arguments.  -maxdepth 0
           means only apply the tests and  actions  to  the  command  line
          arguments.

   -mindepth levels
          Do  not apply any tests or actions at levels less than levels (a
          non-negative integer).  -mindepth  1  means  process  all  files
          except the command line arguments.
5
  • Yeah it's like looking in a mirror 8-)
    – slm
    Oct 2, 2013 at 18:44
  • +1 How both of you got 2 votes is interesting. Cross voting explain 1 each ;) P.S I wanted directory names, so just changing -type f to -type d should solve this, right? Will also wait on slm's response regarding the purpose of -exec basename {}
    – user66001
    Oct 2, 2013 at 19:04
  • @user66001 yes, -type d will find directories. The basename is a very good idea, it will print only the name and remove the path. Assuming you only want names, that's what you should do. Have a look at man basename and also man dirname.
    – terdon
    Oct 2, 2013 at 19:08
  • Thanks terdon - Sorry for not marking your as the answer. Felt that the current version of slm's has more information, for those that need it.
    – user66001
    Oct 2, 2013 at 19:57
  • 2
    @user66001 first of all, you're absolutely right, slm's is indeed better. Second you should never apologize for not accepting, there can be only one and that should be the one that you consider best :).
    – terdon
    Oct 2, 2013 at 21:07
8

The accepted answer works correctly but is somewhat inefficient because it spawns a new basename process for each subdirectory:

find /usr/share/festival/lib/voices -maxdepth 2 -mindepth 2 \
    -type d -exec basename {} \;

When possible, it's preferable to use features built into find to avoid the expense of spawning processes. find has a fairly extensive capability to modify its printed output using the -printf action. The default -print action prints the entire path, but using -printf and a format string it's possible to select portions of the path for printing. To extract just the filename portion of the path without the leading directories (as basename does), the format string is %f. To place a newline after each filename, include \n as follows:

$ find /usr/share/festival/lib/voices -maxdepth 2 -mindepth 2 \
    -type d -printf '%f\n'
nitech_us_awb_arctic_hts
nitech_us_bdl_arctic_hts
nitech_us_slt_arctic_hts
nitech_us_jmk_arctic_hts
nitech_us_clb_arctic_hts
nitech_us_rms_arctic_hts
ked_diphone
kal_diphone
2
  • +1 Thanks for your answer Michael. I can see the advantage to the way of doing this in your answer, also, but given the work put into slm's answer I am at two minds about switching the accepted answer. If @slm sees this, and has no issues with choosing this over his, I will return here to change the accepted answer.
    – user66001
    Jan 25, 2018 at 17:03
  • 2
    @slm's answer is well explained and covers the more general pattern of using find with arbitrary external commands; it's just less efficient for operations that are built into find. I had considered adding a comment to his answer, but that requires more reputation than I have. There's no need to change your accepted answer, as the currently accepted answer is correct, well explained, and usable as a pattern for the more general case; I just wanted to point out that for this specific case there is a more efficient method. Jan 29, 2018 at 13:25
7

TLDR; for those just coming here based on the title of this question; to "List subdirectories only n level[s] deep": use

find -maxdepth N

where N is any number.


Example:

# list all files and folders 4 levels deep
find -maxdepth 4

And if you need to search for a particular file or folder, just pipe it to grep. Ex:

find -maxdepth 4 | grep -i some_file_name

Note that the -i above makes the grep search of the file and folder names coming out of the find command case 'i'nsensitive.

For additional help with find, to make it search only where you want and find only what you want, see also my other answer here: How to exclude a directory in find . command

1
  • I just tried to vote for this, and my vote was rejected! Then I found out I wrote it. :) May 25, 2022 at 6:36
0

For someone using bash looking for something simple just using ls:

ls -d $PWD/*

When creating an alias (in ~/.bash_aliases or wherever), be sure to use single-quotes:

alias ldf='ls -d $PWD/*'

Not quoting it will result in the shell trying to execute ls.

Double-quoting it will create the alias with the value of $PWD at the time of the alias.

You could use $(pwd) if you prefer, but I don't see the point of spawning a sub-shell when bash provides $PWD for you.

2
  • this only lists the directories, you should pass it to | xargs ls, probably?
    – 0x0584
    Sep 21, 2020 at 19:23
  • Alas, this doesn't answer the question (prints all the directories under one path; Question was about printing directory names 2 levels downstream from a base folder, while not folders 1 level downstream from a base folder). Unlike forums, this is a question and answer site. In order to keep the amount of superfluous information to a minimum, only things that directly answer the question usually remain. A separate question of how to list subdirectories of a path would allow this answer, but I trust this less-complicated-than-I-asked question would already be asked and comprehensibly answered.
    – user66001
    Sep 23, 2020 at 9:10
0

In zsh:

print -rC1 -- /usr/share/festival/voices/*/*(N-/:t)

Or the GNU find + sort equivalent:

LC_ALL=C find -L /usr/share/festival/voices -mindepth 2 -maxdepth 2 \
  -name '.*' -prune -o -type d -printf '%f\0' |
  sort -z |
  tr '\0' '\n'

You must log in to answer this question.

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