Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looping over directories in Bash

Tags:

bash

I have a fundamental question about how bash works, and a related practical question.

Fundamental question: suppose I am in a directory that has three subdirectories: a, b, and c.

hen the code

for dir in $(ls)
do 
    echo $dir
done

spits out:

a b c
a b c
a b c

i.e, dir always stores a list of all of the files/directories in my cwd. My question is: why in the world would this be convenient? In my opinion it is far more useful and intuitive to have dir store each element at a time, i.e I would want to have output

a
b
c

Also, as per one of the answers - it is wrong to use for dir in $(ls), but when I use for dir in $(ls -l) I get even more copies of a b c (more than there are directories/files in the cwd). Why is that?

My second question is practical: how do I loop over all the directories (not files!) in my cwd that start with capital W? I started with

for dir in `ls -l W*`

but this fails because a) the reason in question 1 and b) because it doesn't exclude files. Suggestions appreciated.

like image 755
alexvas Avatar asked Apr 06 '13 18:04

alexvas


2 Answers

Never ever parse the output of ls like this (Why you shouldn't parse the output of ls(1)).

Also, your syntax is wrong. You don't mean (), you mean $().

That being said, to loop over directories starting with W you would do (or use the find command instead, depending on your scenario):

for path in /my/path/W*; do
    [ -d "${path}" ] || continue # if not a directory, skip
    dirname="$(basename "${path}")"
    do_stuff
done

As for the output you get from the evil ls-loop, it should not look like that. This is the expected output and demonstrates why you do not want to use ls in the first place:

$ find
.
./c
./a
./foo bar
./b

$ type ls
ls is hashed (/bin/ls)

$ for x in $(ls); do echo "${x}"; done
a
b
c
foo
bar
like image 164
Adrian Frühwirth Avatar answered Oct 13 '22 16:10

Adrian Frühwirth


This should work:

shopt -s nullglob   # empty directory will return empty list
for dir in ./*/;do
    echo "$dir"         # dir is directory only because of the / after *
done

To be recursive in subdirectories too, use globstar:

shopt -s globstar nullglob
for dir in ./**/;do
    echo "$dir" # dir is directory only because of the / after **
done

You can make @Adrian Frühwirths' method to be recursive to sub-directories by using globstar too:

shopt -s globstar
for dir in ./**;do
    [[ ! -d $dir ]] && continue # if not directory then skip
    echo "$dir"
done

From Bash Manual:

globstar

If set, the pattern ‘**’ used in a filename expansion context will match all files and zero or more directories and subdirectories. If the pattern is followed by a ‘/’, only directories and subdirectories match.

nullglob

If set, Bash allows filename patterns which match no files to expand to a null string, rather than themselves.

like image 40
Jahid Avatar answered Oct 13 '22 16:10

Jahid