Bash — проверить, существуют ли файлы и папки в инструкции for

Я пытаюсь распечатать все файлы и папки каталога. Проблема в том, что распечатка if (echo "$f out of if") в порядке, но код никогда не выполняет распечатки внутри операторов if.

Я уверен, что файлы и папки существуют (распечатаны, если), и я проверил разрешения.

dirPath — это /tmp/testFolder.

for f in `ls $dirPath`; do
   echo "$f out of if"
   if [ -d $f ]; then
      echo "$f is a directory"
   fi
   if [ -f $f ]; then
      echo "$f is a file"
   fi
done

Обновлено: Всем спасибо за ответ!! Я попробовал это решение, но обнаружил проблему, заключающуюся в том, что «tmp/testFolder» (dirPath) также распознается как папка в цикле for (и мне нужно найти только подпапку).:

shopt -s globstar

for f in "$dirPath"/**; do
   echo "$f out of if"
   if [ -d $f ]; then
      echo "$f is a directory"
   fi
   if [ -f $f ]; then
      echo "$f is a file"
   fi
done
for f in `ls ...`сломан.
melpomene 24.01.2019 23:04

Что выдает этот скрипт?

melpomene 24.01.2019 23:05

Вывод: "namefile1 вне if", "namefile2 вне if", "namedir вне if"

Elena Guidi 24.01.2019 23:07
Никогда использовать for i in $(ls anything), см. Подводные камни Баша #1 (это причина, которая является ловушкой №1) (подсказка - один только ls не спускается в подкаталоги) Используйте find или включите globstar (shopt -s globstar) и используйте for f in "$dirPath"/**
David C. Rankin 24.01.2019 23:08

Привет @DavidC.Rankin. Мне нравится твое элегантное решение с globstar... но оно не работает в моем случае (не знаю почему). Моя версия bash: GNU bash, версия 4.4.12(1)-выпуск (x86_64-pc-linux-gnu)

Elena Guidi 24.01.2019 23:36

Отредактируйте @DavidC.Rankin, он не запускается, но также проверяет "tmp/testFolder" ... и не только "tmp/testFolder/something"

Elena Guidi 24.01.2019 23:44

Елена, у меня почему-то такое ощущение, что у нас тут XY проблема. Вы спрашиваете о X, но хотите решить Y. Если это просто забавный сценарий, я могу ошибаться. Но если вы спросите что-то вроде «копировать все файлы, но игнорировать каталоги» или «сохранить все файлы, но удалить все каталоги» или что-то в этом роде, вы можете получить совершенно другие (и более эффективные) ответы.

Ralf 25.01.2019 09:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
152
5

Ответы 5

Проблема с вашим кодом заключается в том, что переменная f не содержит полного пути к файлу (файлам) в вашем целевом каталоге. Поэтому, если вы не укажете текущий каталог, тесты для «каталога» и «файла» всегда будут терпеть неудачу.

Вы можете использовать префикс $dirPath в своих операторах if (например, if [ -d "$dirPath/$f" ]; then echo "$dirPath/$f is a directory"; fi). Или, что еще лучше, вместо этого используйте расширение glob в цикле for, которое даст вам полный путь к каждому файлу.

#!/bin/bash

for f in "$dirPath"/*; do
   if [ -d "$f" ]; then
      echo "$f is a directory"
   elif [ -f "$f" ]; then
      echo "$f is a file"
   fi
done

Привет! Спасибо за ответ. Я пробовал с расширением glob, и у меня проблема, что «он» тоже проверяет «tmp/testFolder»... и не только «tmp/testFolder/something».

Elena Guidi 24.01.2019 23:43

@ElenaGuidi Я не уверен, что понял твой комментарий. Является ли /tmp/testFolder вашим dirPath? И что вы подразумеваете под "он" проверить?

P.P 24.01.2019 23:48

Я имею в виду, что в коде for не только проверяются файлы и каталоги внутри testFolder, но и тестируется testFolder. Посмотрите на вывод: tmp/testFolder/ out of if /tmp/testFolder/ is a directory ### Мне не нужны 2 строки перед этим комментарием ## /tmp/testFolder/folder1 out of if /tmp/testFolder/folder 2 — это каталог

Elena Guidi 25.01.2019 00:18

@ElenaGuidi Это должно случиться. Вы уверены, что у вас тот же код, что и в моем ответе? Можете ли вы попробовать скопировать этот код в новый скрипт и снова протестировать?

P.P 25.01.2019 09:24

Проблема в том, что $f — это относительный путь. Итак, $f — это test.file, но вы хотите проверить /tmp/testFolder/test.file. Правильным решением будет:

for f in "$dirPath"/*; do
   echo "$f out of if"
   if [ -d "$f" ]; then
      echo "$f is a directory"
   fi
   if [ -f "$f" ]; then
      echo "$f is a file"
   fi
done

Не будет расширять подкаталоги $dirPath, и если $dirPath содержит пробелы --- произойдет что-то плохое. Процитируйте свои переменные for f in "$dirPath"/*; -- лучше shopt -s globstar; for f in "$dirPath/**"; do

David C. Rankin 24.01.2019 23:12

Привет! @Ralf Спасибо за ответ! Я попробовал ваше решение, но вот результат: (первая часть)/tmp/testFolder — это каталог /bin out /bin — это каталог /boot out /boot — это каталог /cdrom out /cdrom — это каталог /dev out/dev это каталог /etc out /etc это каталог /home out /home это каталог /initrd.img out /initrd.img это файл /initrd.img.old out /initrd.img.old это файл

Elena Guidi 24.01.2019 23:14

@ElenaGuidi Есть ли пробел в конце $dirPath?

melpomene 24.01.2019 23:16

Все /bin /boot /cdrom /dev /etc /home ... находятся вне вашего (корневого) каталога / -- что-то идет не так.

David C. Rankin 24.01.2019 23:18

@melpomene без пробелов, во всяком случае, я попытался указать путь типа «tmp/testFolder» без переменной, чтобы быть уверенным

Elena Guidi 24.01.2019 23:33

@ElenaGuidi Тогда вам нужно показать свой реальный код.

melpomene 24.01.2019 23:34

Добавлены цитаты @DavidC.Rankin. Рекурсивного не было в самом вопросе.

Ralf 24.01.2019 23:38

@Ralf, это было немного неясно, я согласен, и я до сих пор не совсем уверен, хотим ли мы спуститься дальше, но, тем не менее, хорошая работа с обновлением, кавычки избавят от многих проблем с именем файла.

David C. Rankin 25.01.2019 00:25

Код в if не выполняется, потому что в текущем каталоге нет namefile1, что и проверяет ваш код. То есть $f не включает $dirPath (префикс каталога) в том виде, в котором написан ваш код.

Лучше:

for f in "$dirPath"/*; do
    echo "$f out of if"
    if [ -d "$f" ]; then
        echo "$f is a directory"
    fi
    if [ -f "$f" ]; then
        echo "$f is a file"
    fi
done

См. также следующий список подводных камней bash:

  • for f in $(ls *.mp3)
  • [ -n $foo ] или [ -z $foo ]

Непосредственная проблема заключается в том, что f задается только именем файла, а не полным путем. То есть, если /tmp/testFolder содержит файл с именем example.txt, f будет установлен просто как «example.txt», поэтому if [ -d $f ]; then проверяет наличие файла с именем «example.txt» в текущем рабочем каталоге, а не в / tmp/тестовая папка. Одним из вариантов было бы использование if [ -d $dirPath/$f ]; then, но есть и лучший способ.

Как правило, вы используете не следует анализировать вывод ls, потому что его вывод неоднозначен по нескольким причинам. Чтобы получить список файлов в определенном каталоге, просто используйте dirpath/* — он получает список соответствующих файлов без какой-либо двусмысленности или проблем с разбором, которые вы столкнетесь с ls. Бонус: он включает указанный путь как часть результата (например, /tmp/testFolder/* может дать «/tmp/testFolder/example.txt»).

Еще одно предложение: вы должны (почти) всегда помещать ссылки на переменные в двойные кавычки, например. "$f" вместо просто $f.

Исправление всего этого дает:

for f in "$dirPath"/*; do    # Note that $dirPath should be quoted, but * cannot be
   echo "$f out of if"
   if [ -d "$f" ]; then
      echo "$f is a directory"
   fi
   if [ -f "$f" ]; then
      echo "$f is a file"
   fi
done

Обратите внимание, что команды echo также дадут полный путь. Если вы этого не хотите, вы можете использовать команду basename, чтобы получить только часть имени, например. f_name = "$(basename "$f")" или (как указал @melpomene) используйте расширение "${f##*/}" (которое сокращается до последнего «/» в переменной):

for f in "$dirPath"/*; do
   f_name = "${f##*/}"    # The quotes are not strictly needed in an assignment, but do no harm
   echo "$f_name out of if"
   if [ -d "$f" ]; then
      echo "$f_name is a directory"
   fi
   if [ -f "$f" ]; then
      echo "$f_name is a file"
   fi
done

О, и есть один возможный недостаток использования подстановочного знака вместо ls: он вернет необработанный подстановочный знак, если совпадений нет. В данном случае это не имеет значения, но в тех местах, где это происходит, вы можете либо запустить цикл с помощью [ -e "$f" ] || continue (т. е. пропустить цикл, если на самом деле там ничего нет), либо, если вы используете bash (а не только общую оболочку), вы можно установить опцию оболочки nullglob (shopt -s nullglob).

Еще одна рекомендация: shellcheck.net хорошо выявляет распространенные ошибки сценариев, поэтому я рекомендую запускать ваши сценарии через него для получения рекомендаций.

"${f##*/}" сохраняет порождение процесса basename.
melpomene 24.01.2019 23:23

@melpomene Есть несколько случаев (в основном, когда путь заканчивается на «/»), когда это не работает, поэтому я обычно использую basename. Но я не думаю, что это может произойти здесь, поэтому я добавлю это как вариант.

Gordon Davisson 25.01.2019 00:04

Основываясь на вашем редактировании, вам нужно только сравнить, равен ли "$f""$dataPath/", чтобы обнаружить и избежать печати каталога "$dataPath". (фактически используя !=) Продолжая ваш пример (и включая все комментарии о том, что for i inls ничего не использует `...), вы можете сделать:

#!/bin/bash

shopt -s globstar   ## enable globstar

[ -z "$1" ] && {    ## validate at least 1 argument provided
    printf "error: insufficient input\nusage: %s path\n" "${0##*/}" >&2
    exit 1
}

datapath = "$1"       ## set datapath

[ -d "$datapath" ] || { ## validate argument is a directory
    printf "error: invalid path - not a directory.\n" >&2
    exit 1;
}

## output absolute file/directory names
for f in "$datapath"/**; do     ## find files & subdirs below datapath
    if [ -d "$f" ]; then        ## am I a directory that isn't datapath
        [ "$f" != "$datapath/" ] && printf "directory: %s\n" "$f"
    elif [ -f "$f" ]; then      ## am I a file?
        printf "filename : %s\n" "$f"
    else                        ## am I neither a file or directory?
        printf "unknown  : %s\n" "$f"
    fi
done

Теперь, если вам нужны только относительные имена файлов ниже "$dataPath", вам просто нужно обрезать "dataPath/" в начале каждого имени файла, используя расширение параметра${parameter#[word]} (где [word] расширяется до шаблона, который вы хотите сопоставить, например, "$dataPath/" здесь):

printf "\nwith relative path\n\n"
## output relative file/directory names
for f in "$datapath"/**; do
    relpath = "${f#$datapath/}"   ## trim $datapath/ from f
    if [ -d "$f" ]; then
        [ -n "$relpath" ] && printf "directory: %s\n" "$relpath"
    elif [ -f "$f" ]; then
        printf "filename : %s\n" "$relpath"
    else
        printf "unknown  : %s\n" "$relpath"
    fi
done

Пример, включающий оба вышеперечисленных в следующей структуре каталогов:

Пример каталога

$ tree ~/dev/src-c/tmp/tst/sha2dcr
/home/david/dev/src-c/tmp/tst/sha2dcr
├── chkpass.c
├── dat
│   ├── pw
│   ├── users.bin
│   ├── users.bin.sav
│   └── users.txt
├── getpass.c
├── getpass.h
├── readuserdb.c
├── sha256d.c
├── sha256d.h
├── sha256dtst.c
├── sha256fread.c
├── sha256fread_timed.c
└── userdb.c

Пример использования/вывода сценария

Включая как абсолютные, так и относительные пути, у вас будет:

$ bash ~/scr/utl/filelist.sh ~/dev/src-c/tmp/tst/sha2dcr
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/chkpass.c
directory: /home/david/dev/src-c/tmp/tst/sha2dcr/dat
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/dat/pw
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/dat/users.bin
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/dat/users.bin.sav
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/dat/users.txt
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/getpass.c
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/getpass.h
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/readuserdb.c
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/sha256d.c
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/sha256d.h
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/sha256dtst.c
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/sha256fread.c
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/sha256fread_timed.c
filename : /home/david/dev/src-c/tmp/tst/sha2dcr/userdb.c

with relative path

filename : chkpass.c
directory: dat
filename : dat/pw
filename : dat/users.bin
filename : dat/users.bin.sav
filename : dat/users.txt
filename : getpass.c
filename : getpass.h
filename : readuserdb.c
filename : sha256d.c
filename : sha256d.h
filename : sha256dtst.c
filename : sha256fread.c
filename : sha256fread_timed.c
filename : userdb.c

(Заметка: вывод directory: /home/david/dev/src-c/tmp/tst/sha2dcr/ подавляется в обоих случаях)

Просмотрите все и дайте мне знать, если у вас есть дополнительные вопросы.

(примечание: я использую все имена переменных в нижнем регистре, если у вас есть необъяснимая ошибка копирования...)

Другие вопросы по теме