Bash ls – List Subdirectories Only N Levels Deep

bashls

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?

Best Answer

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.

Related Question