Bash, переключение по каталогам

У меня есть фундаментальный вопрос о том, как работает bash, и связанный с ним практический вопрос.

Фундаментальный вопрос: предположим, что я в директории с тремя подкаталогами: a, b и c.

с кодом

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

выплевывает:

a b c
a b c
a b c

i.e, dir всегда хранит список всех файлов/каталогов в моем cwd. Мой вопрос: почему в мире это было бы удобно? На мой взгляд, гораздо более полезно и интуитивно иметь dir хранить каждый элемент за раз, т.е. Я хотел бы иметь вывод

a
b
c

Кроме того, согласно одному из ответов - неправильно использовать for dir in $(ls), но когда я использую for dir in $(ls -l), я получаю еще больше копий a b c (больше, чем есть каталоги/файлы в cwd). Почему это?

Мой второй вопрос практичен: как мне перебирать все каталоги (а не файлы!) в моем cwd, которые начинаются с капитала W? Я начал с

for dir in `ls -l W*`

но это не удается, потому что: a) причина в вопросе 1 и b), поскольку она не исключает файлы. Предложения оценены.

Ответ 1

Никогда не анализируйте вывод ls следующим образом (Почему вы не должны разбирать вывод ls (1)).

Кроме того, ваш синтаксис неверен. Вы не имеете в виду (), вы имеете в виду $().

Чтобы сказать, что для перебора каталогов, начинающихся с W, вы будете делать (или вместо этого используйте команду find, в зависимости от вашего сценария):

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

Что касается вывода, который вы получаете от злого ls-цикла, он не должен выглядеть так. Это ожидаемый результат и демонстрирует, почему вы не хотите использовать ls в первую очередь:

$ 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

Ответ 2

Это должно работать:

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

Чтобы быть рекурсивным в подкаталогах, используйте globstar:

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

Вы можете сделать метод @Adrian Frühwirths рекурсивным для подкаталогов с помощью globstar тоже:

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

Из Bash Вручную:

globstar

Если установлено, шаблон '**, используемый в контексте расширения имени файла, будет сопоставлять все файлы и ноль или более каталогов и подкаталогов. Если за шаблоном следуют "/, только каталоги и подкаталоги матч.

nullglob

Если установлено, Bash разрешает шаблоны имен файлов, которые не соответствуют файлам для расширения к пустой строке, а не к себе.

Ответ 3

Хорошо, вы знаете, что вы видите, это не то, что вы ожидаете. Результат, который вы видите, не из команды echo, а из команды dir.

Попробуйте следующее:

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