Masquue
Masquue

Reputation: 322

Listing only directories using ls in Bash but preserve ls format and without dirname

There are already many answers for this similar question. But none of them satisfy my requirements. I want

  1. List all directories under a directory without using glob (*) syntax, i.e. I want to directly use lsdir somedir

  2. Output should containing basename of the directories like when you just use ls, like:

    $ lsdir path/to/some/dir
    dir1 dir2 dir3 dir4
    

    but not this:

    $ lsdir path/to/dir
    path/to/dir/dir1 path/to/dir/dir2 path/to/dir/dir3 path/to/dir/dir4
    

    To satisfy requirement 1, it seems feasible to define a function, but anyway we are going to use -d option, to list the directories themselves of the ls command parameters.

    And when using -d option, ls list directory names with its parent prepended, like above.

  3. ls format (color, align, sort) should be preserved.

    To satisfy requirement 2, we can use find but in this way we lose all the ls output format, like coloring (based on customized dircolors theme), alignment (output in aligned columns), sorting (sorting customized with various flags and in a column-first manner), and maybe some other things.

I know it's too greedy to want this many features simultaneously, and indeed I can live without all of them.

It's possible to emulate ls output format manually but that's too inconsistent.

I wonder if there is a way to achieve this and still utilize ls, i.e. how to achieve requirement 2 using ls.

Upvotes: 0

Views: 839

Answers (2)

Masquue
Masquue

Reputation: 322

Based on @M. Nejat Aydin's excellent answer, I am going to improve a little more to make it a useful command, especially with respect to processing options and multiple directories.

list_directories() {
  local opts=()
  local args=()
  for i in $(seq $#); do
    if [[ "${!i}" == -* ]]; then
      opts+=("${!i}")
    else
      args+=("${!i}")
    fi
  done
  (( ${#args[@]} == 0 )) && args=('.')
  local -i args_n_1=${#args[@]}-1
  for i in $(seq 0 $args_n_1); do
    if (( ${#args[@]} > 1 )); then
      (( i > 0 )) && echo
      echo "${args[i]}:"
    fi
    (
      shopt -s nullglob 
      cd "${args[i]}" && 
      dirs=(*/) && 
      (( ${#dirs[@]} > 0 )) && 
      ls -d "${opts[@]}" "${dirs[@]%?}" 
    )
  done
}
alias lsd=list_directories

This lsd can be used with any number of ls options and directories freely mixed.

$ lsd -h dir1 dir2 -rt ~

Note: Semantic meaning changes when you use globs with lsd.

lsd path/to/dir* list all directories under each directory starting with "path/to/dir".
To list all directories starting with "path/to/dir", use plain old ls -d path/to/dir*.

Upvotes: 0

M. Nejat Aydin
M. Nejat Aydin

Reputation: 10123

This may be what you're looking for:

cd path/to/dir && dirs=(*/) && ls -d "${dirs[@]%?}"

or, perhaps

(shopt -s nullglob; cd path/to/dir && dirs=(*/) && ((${#dirs[@]} > 0)) && ls -d "${dirs[@]%?}")

The second version runs in a subshell and prints nothing if there is no any subdirectory inside path/to/dir.

Upvotes: 1

Related Questions