0

I am trying to print the number of directories in the working directory. For some reason my counter is not incrementing. Can you please tell me why?

#!/bin/bash
n=0
for afile in $(ls)
do
    if [ -d $afile ]
    then
      (( n ++ ))
    fi
done
echo There are $n directories under the current directory $(pwd).
exit 0

Oddly, this program seems to iterate through ls .. (up directory)

for myfile in $(ls ..)
do
    if [ -d "../$myfile" ]
    then
      echo "../$myfile (dir)"
    else
      echo ../$myfile
    fi
done
exit 0
7
  • Sorry, I think we edited at the same time. You might want to add back your changes.
    – Sparhawk
    Mar 2, 2018 at 0:37
  • Are you sure? I just refreshed and the code block looks OK now.
    – Benjamin
    Mar 2, 2018 at 0:38
  • Oh okay, maybe we made the same change anyway!
    – Sparhawk
    Mar 2, 2018 at 0:38
  • So when you run this, is the output always 0 directories? It works for me, although you need to quote properly.
    – Sparhawk
    Mar 2, 2018 at 0:39
  • Yes, it's always 0. Can you explain what is wrong with my quotes?
    – Benjamin
    Mar 2, 2018 at 0:41

2 Answers 2

1

For counting the number of non-hidden directories (in the current directory), using bash:

shopt -s nullglob
set -- */
printf 'There are %d non-hidden subdirectories in %s\n' "$#" "$PWD"

To include the count of hidden directories:

shopt -s dotglob nullglob
set -- */
printf 'There are %d subdirectories in %s\n' "$#" "$PWD"

What these pieces of code do is to expand the pattern */ and to count the number of names that the pattern expands to. The pattern, since it ends with a slash, will only expand to directory names (or to names of symbolic links to directories).

The directory names will be assigned to the positional parameters, $1, $2 etc. using set, and the number of these parameters is kept in $# by the shell (so there's no need to actually loop over them to count them).

If you feel more comfortable with bash arrays:

shopt -s dotglob nullglob
dirs=( */ )
printf 'There are %d subdirectories in %s\n' "${#dirs[@]}" "$PWD"

This is essentially the same thing, except it uses a named array instead of the positional parameters.

The dotglob shell option, in bash, will make * match hidden names as well as non-hidden names. The nullglob shell option will make non-matching patterns expand to nothing.

Related:

2
  • Your solution will print 1 in the case where there are no subdirectories. You may use the unportable nullglob; shopt -s nullglob; set -- */ .[!.]/ .??*/.
    – user313992
    Jan 11, 2019 at 12:07
  • @UncleBilly Thanks. It should be enough with nullglob for bash though, and a test on $1 in /bin/sh (which I'm not covering here). I will update ASAP.
    – Kusalananda
    Jan 11, 2019 at 12:14
0

Your code works fine for me, but there are a few errors in it that will fail in some cases (e.g. if a filename has a space in it).

  • Firstly, don't parse ls. Instead iterate using a glob. i.e. instead of $(ls), use *.
  • When reading variables, you should quote them, i.e. use "$foo" instead of $foo. If you do not quote them, the shell will split the output on the IFS (i.e. the spaces). You can test this with your script. Include some directories with no spaces, then test with a space. Only the former will be counted.

Here is the fixed code:

#!/bin/bash
n=0
for afile in *
do
    if [ -d "$afile" ]
    then
      (( n ++ ))
    fi
done
echo There are $n directories under the current directory $(pwd).
exit 0

I'm not sure if this is obvious, but the script will run in the current directory.

3
  • Thanks Sparhawk! That definitely works. But in my assignment I am required to use the ls command. Is there ANY reasonable way to parse and iterate through ls?
    – Benjamin
    Mar 2, 2018 at 1:04
  • 1
    @Benjamin not really. This is an example: unix.stackexchange.com/q/128985/117549
    – Jeff Schaller
    Mar 2, 2018 at 1:09
  • I agree that there's no way to parse ls that works for all files. You might be able to use something like find, but that probably not allowed either. One hacky way to count directories with ls is to use ls -F (see man ls) and search for /.
    – Sparhawk
    Mar 2, 2018 at 1:43

You must log in to answer this question.

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