Listing Only Directories Using Ls in Bash?

This command lists directories in the current path: ls -d */

What exactly does the pattern */ do?

And how can we give the absolute path in the above command (e.g. ls -d /home/alice/Documents) for listing only directories in that path?

0

29 Answers

*/ is a pattern that matches all of the subdirectories in the current directory (* would match all files and subdirectories; the / restricts it to directories). Similarly, to list all subdirectories under /home/alice/Documents, use ls -d /home/alice/Documents/*/

7

Four ways to get this done, each with a different output format

1. Using echo

Example: echo */, echo */*/
Here is what I got:

cs/ draft/ files/ hacks/ masters/ static/  
cs/code/ files/images/ static/images/ static/stylesheets/  

2. Using ls only

Example: ls -d */
Here is exactly what I got:

cs/     files/      masters/  
draft/  hacks/      static/  

Or as list (with detail info): ls -dl */

3. Using ls and grep

Example: ls -l | grep "^d" Here is what I got:

drwxr-xr-x  24 h  staff     816 Jun  8 10:55 cs  
drwxr-xr-x   6 h  staff     204 Jun  8 10:55 draft  
drwxr-xr-x   9 h  staff     306 Jun  8 10:55 files  
drwxr-xr-x   2 h  staff      68 Jun  9 13:19 hacks  
drwxr-xr-x   6 h  staff     204 Jun  8 10:55 masters  
drwxr-xr-x   4 h  staff     136 Jun  8 10:55 static  

4. Bash Script (Not recommended for filename containing spaces)

Example: for i in $(ls -d */); do echo ${i%%/}; done
Here is what I got:

cs  
draft  
files  
hacks  
masters  
static

If you like to have '/' as ending character, the command will be: for i in $(ls -d */); do echo ${i}; done

cs/  
draft/  
files/  
hacks/  
masters/  
static/
9

I use:

ls -d */ | cut -f1 -d'/'

This creates a single column without a trailing slash - useful in scripts.

7

For all folders without subfolders:

find /home/alice/Documents -maxdepth 1 -type d

For all folders with subfolders:

find /home/alice/Documents -type d
2

Four (more) Reliable Options.

An unquoted asterisk * will be interpreted as a pattern (glob) by the shell. The shell will use it in pathname expansion. It will then generate a list of filenames that match the pattern.

A simple asterisk will match all filenames in the PWD (present working directory). A more complex pattern as */ will match all filenames that end in /. Thus, all directories. That is why the command:

1.- echo.

echo */
echo ./*/              ### Avoid misinterpreting filenames like "-e dir"

will be expanded (by the shell) to echo all directories in the PWD.


To test this: Create a directory (mkdir) named like test-dir, and cd into it:

mkdir test-dir; cd test-dir

Create some directories:

mkdir {cs,files,masters,draft,static}   # Safe directories.
mkdir {*,-,--,-v\ var,-h,-n,dir\ with\ spaces}  # Some a bit less secure.
touch -- 'file with spaces' '-a' '-l' 'filename'    # And some files:

The command echo ./*/ will remain reliable even with odd named files:

./--/ ./-/ ./*/ ./cs/ ./dir with spaces/ ./draft/ ./files/ ./-h/
./masters/ ./-n/ ./static/ ./-v var/

But the spaces in filenames make reading a bit confusing.


If instead of echo, we use ls. The shell is still what is expanding the list of filenames. The shell is the reason to get a list of directories in the PWD. The -d option to ls makes it list the present directory entry instead of the contents of each directory (as presented by default).

ls -d */

However, this command is (somewhat) less reliable. It will fail with the odd named files listed above. It will choke with several names. You need to erase one by one till you find the ones with problems.

2.- ls

The GNU ls will accept the "end of options" (--) key.

ls -d ./*/                     ### More reliable BSD ls
ls -d -- */                    ### More reliable GNU ls

3.-printf

To list each directory in its own line (in one column, similar to ls -1), use:

$ printf "%s\n" */        ### Correct even with "-", spaces or newlines.

And, even better, we could remove the trailing /:

$ set -- */; printf "%s\n" "${@%/}"        ### Correct with spaces and newlines.

An attempt like

$ for i in $(ls -d */); do echo ${i%%/}; done

will fail on:

  • some names (ls -d */) as already shown above.
  • will be affected by the value of IFS.
  • will split names on spaces and tabs (with default IFS).
  • each newline in the name will start a new echo command.

4.- Function

Finally, using the argument list inside a function will not affect the arguments list of the present running shell. Simply

$ listdirs(){ set -- */; printf "%s\n" "${@%/}"; }
$ listdirs

presents this list:

--
-
*
cs
dir with spaces
draft
files
-h
masters
-n
static
-v var

These options are safe with several types of odd filenames.

0

The tree command is also pretty useful here. By default it will show all files and directories to a complete depth, with some ASCII characters showing the directory tree.

$ tree
.
├── config.dat
├── data
│   ├── data1.bin
│   ├── data2.inf
│   └── sql
|   │   └── data3.sql
├── images
│   ├── background.jpg
│   ├── icon.gif
│   └── logo.jpg
├── program.exe
└── readme.txt

But if we wanted to get just the directories, without the ASCII tree, and with the full path from the current directory, you could do:

$ tree -dfi
.
./data
./data/sql
./images

The arguments being:

-d     List directories only.
-f     Prints the full path prefix for each file.
-i     Makes tree not print the indentation lines, useful when used in conjunction with the -f option.

And if you then want the absolute path, you could start by specifying the full path to the current directory:

$ tree -dfi "$(pwd)"
/home/alice/Documents
/home/alice/Documents/data
/home/alice/Documents/data/sql
/home/alice/Documents/images

And to limit the number of subdirectories, you can set the max level of subdirectories with -L level, e.g.:

$ tree -dfi -L 1 "$(pwd)"
/home/alice/Documents
/home/alice/Documents/data
/home/alice/Documents/images

More arguments can be seen with man tree.

In case you're wondering why output from 'ls -d */' gives you two trailing slashes, like:

[prompt]$ ls -d */
app//  cgi-bin//  lib//        pub//

it's probably because somewhere your shell or session configuration files alias the ls command to a version of ls that includes the -F flag. That flag appends a character to each output name (that's not a plain file) indicating the kind of thing it is. So one slash is from matching the pattern '*/', and the other slash is the appended type indicator.

To get rid of this issue, you could of course define a different alias for ls. However, to temporarily not invoke the alias, you can prepend the command with backslash:

\ls -d */
Maya Lin-Takahashi

Maya Lin-Takahashi

Consumer Tech & Gadget Reviewer

Maya is a hardware enthusiast who tests and reviews smart home devices, smartphones, wearables, and audio gear. She focuses on practical consumer value and build quality.