Я пытаюсь распечатать все файлы и папки каталога.
Проблема в том, что распечатка 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
Что выдает этот скрипт?
Вывод: "namefile1 вне if", "namefile2 вне if", "namedir вне if"
for i in $(ls anything), см. Подводные камни Баша #1 (это причина, которая является ловушкой №1) (подсказка - один только ls не спускается в подкаталоги) Используйте find или включите globstar (shopt -s globstar) и используйте for f in "$dirPath"/**Привет @DavidC.Rankin. Мне нравится твое элегантное решение с globstar... но оно не работает в моем случае (не знаю почему). Моя версия bash: GNU bash, версия 4.4.12(1)-выпуск (x86_64-pc-linux-gnu)
Отредактируйте @DavidC.Rankin, он не запускается, но также проверяет "tmp/testFolder" ... и не только "tmp/testFolder/something"
Елена, у меня почему-то такое ощущение, что у нас тут XY проблема. Вы спрашиваете о X, но хотите решить Y. Если это просто забавный сценарий, я могу ошибаться. Но если вы спросите что-то вроде «копировать все файлы, но игнорировать каталоги» или «сохранить все файлы, но удалить все каталоги» или что-то в этом роде, вы можете получить совершенно другие (и более эффективные) ответы.





Проблема с вашим кодом заключается в том, что переменная 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».
@ElenaGuidi Я не уверен, что понял твой комментарий. Является ли /tmp/testFolder вашим dirPath? И что вы подразумеваете под "он" проверить?
Я имею в виду, что в коде for не только проверяются файлы и каталоги внутри testFolder, но и тестируется testFolder. Посмотрите на вывод: tmp/testFolder/ out of if /tmp/testFolder/ is a directory ### Мне не нужны 2 строки перед этим комментарием ## /tmp/testFolder/folder1 out of if /tmp/testFolder/folder 2 — это каталог
@ElenaGuidi Это должно случиться. Вы уверены, что у вас тот же код, что и в моем ответе? Можете ли вы попробовать скопировать этот код в новый скрипт и снова протестировать?
Проблема в том, что $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
Привет! @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 это файл
@ElenaGuidi Есть ли пробел в конце $dirPath?
Все /bin /boot /cdrom /dev /etc /home ... находятся вне вашего (корневого) каталога / -- что-то идет не так.
@melpomene без пробелов, во всяком случае, я попытался указать путь типа «tmp/testFolder» без переменной, чтобы быть уверенным
@ElenaGuidi Тогда вам нужно показать свой реальный код.
Добавлены цитаты @DavidC.Rankin. Рекурсивного не было в самом вопросе.
@Ralf, это было немного неясно, я согласен, и я до сих пор не совсем уверен, хотим ли мы спуститься дальше, но, тем не менее, хорошая работа с обновлением, кавычки избавят от многих проблем с именем файла.
Код в 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:
Непосредственная проблема заключается в том, что 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 Есть несколько случаев (в основном, когда путь заканчивается на «/»), когда это не работает, поэтому я обычно использую basename. Но я не думаю, что это может произойти здесь, поэтому я добавлю это как вариант.
Основываясь на вашем редактировании, вам нужно только сравнить, равен ли "$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/ подавляется в обоих случаях)
Просмотрите все и дайте мне знать, если у вас есть дополнительные вопросы.
(примечание: я использую все имена переменных в нижнем регистре, если у вас есть необъяснимая ошибка копирования...)
for f in `ls ...`сломан.