简体   繁体   中英

Looping over directories in 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

ie, 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, ie 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 abc (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.

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

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.

Well, you know what you are seeing is not what you are expecting. The output you are seeing is not from the echo command, but from the dir command.

Try the following:

ls -1 | while read line; do 

   if [-d "$line" ] ; then 
      echo $line
   fi

done


for files in $(ls) ; do

   if [-d "$files" ] ; then 
      echo $files
   fi

done

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM