Скрипты Bash: тест для пустой директории

95

Я хочу проверить, не содержит ли каталог никаких файлов. Если так, я пропущу некоторую обработку.

Я попробовал следующее:

if [ ./* == "./*" ]; then
    echo "No new file"
    exit 1
fi

Это дает следующую ошибку:

line 1: [: too many arguments

Есть ли решение / альтернатива?

Энтони Конг
источник
связанные: stackoverflow.com/q/91368/52074
Тревор Бойд Смит

Ответы:

121
if [ -z "$(ls -A /path/to/dir)" ]; then
   echo "Empty"
else
   echo "Not Empty"
fi

Также было бы здорово проверить, существует ли каталог раньше.

Андрей Атапин
источник
11
Не используйте &&и ||одновременно! Если echo "Not Empty"не echo "Empty"получится, побежит! Попробуй echo "test" && false || echo "fail"! Да, я знаю, echoчто не подведет, но если вы измените любую другую команду, вы будете удивлены!
uzsolt
4
Пожалуйста, приведите хотя бы один пример, когда приведенный выше код не будет работать. Конкретно этот код абсолютно корректен. Я надеюсь, что Аскер сможет адаптировать это для своих целей
Андрей Атапин
3
[ "$(ls -A /)" ] && touch /non-empty || echo "Empty"- если вы хотите «пометить» непустые каталоги созданным файлом с именем non-empty, произойдет сбой и будет напечатано «Пусто».
uzsolt
4
где touch /emptyв моей линии?
Андрей Атапин
7
о нет! Есть очень важная проблема с этим кодом. Это должно быть if [ -n "$(ls -A /path/to/dir)" ]; then... Пожалуйста, обновите ответ, прежде чем кто-то вставит этот код в свой сервер и хакер поймет, как его использовать. Если /path/to/dirне пусто, то имена файлов там передаются как аргументы, /bin/testкоторые явно не предназначены. Просто добавьте -nаргумент, проблема решена. Спасибо!
Эдвард Нед Харви
21

Нет необходимости считать что-либо или ракушки. Вы также можете использовать readв сочетании с find. Если findвывод пуст, вы вернете false:

if find /some/dir -mindepth 1 | read; then
   echo "dir not empty"
else
   echo "dir empty"
fi

Это должно быть портативным.

slhck
источник
Хорошее решение, но я думаю, что ваши echoзвонки отражают неправильный результат: в моем тесте (под Cygwin) find . -mindepth 1 | readбыло 141 код ошибки в
Lucas
@ LucasCimon Не здесь (macOS и GNU / Linux). Для непустого каталога readвозвращает 0, а для пустого - 1.
slhck
1
PSA не работает сset -o pipefail
полковник Тридцать два
19
if [ -n "$(find "$DIR_TO_CHECK" -maxdepth 0 -type d -empty 2>/dev/null)" ]; then
    echo "Empty directory"
else
    echo "Not empty or NOT a directory"
fi
uzsolt
источник
Правильно и быстро. Приятно!
10
4
Требуются кавычки (2x) и проверка -nна правильность и безопасность (проверка с каталогом с пробелами в имени, проверка с непустым каталогом с именем '0 = 1'). ... [ -n "$(find "$DIR_TO_CHECK" -maxdepth 0 -type d -empty 2>/dev/null)" ]; ...
Зрин
1
@ivan_pozdeev Это не так, по крайней мере, для GNU найти. Вы можете думать о grep. serverfault.com/questions/225798/…
Владимир Пантелеев
Это может быть проще написать find "$DIR_TO_CHECK" -maxdepth 0 -type d -empty | grep ., и полагаться на статус выхода из grep. Как бы вы это ни делали, это очень правильный ответ на этот вопрос.
Том Андерсон
13
#!/bin/bash
if [ -d /path/to/dir ]; then
    # the directory exists
    [ "$(ls -A /path/to/dir)" ] && echo "Not Empty" || echo "Empty"
else
    # You could check here if /path/to/dir is a file with [ -f /path/to/dir]
fi
Renaud
источник
4
Это должно быть так, не нужно разбирать вывод ls, просто посмотрите, пустой он или нет. Использование находки для меня кажется излишним.
Акостадинов
4

Это сделает работу в текущем рабочем каталоге (.):

[ `ls -1A . | wc -l` -eq 0 ] && echo "Current dir is empty." || echo "Current dir has files (or hidden files) in it."

или та же команда разделена на три строки, чтобы быть более читабельной:

[ `ls -1A . | wc -l` -eq 0 ] && \
echo "Current dir is empty." || \
echo "Current dir has files (or hidden files) in it."

Просто замените ls -1A . | wc -lна, ls -1A <target-directory> | wc -lесли вам нужно запустить его в другой целевой папке.

Edit : я заменил -1aс -1A(см @Daniel комментарий)

ztank1013
источник
2
используйте ls -Aвместо Некоторые файловые системы не имеют .и ..символических ссылок в соответствии с документами.
Даниэль Бек
1
Спасибо @Daniel, я отредактировал свой ответ после твоего предложения. Я знаю, что "1" тоже может быть удален.
ztank1013
2
Это не больно, но подразумевается, если вывести его не на терминал. Поскольку вы передаете его в другую программу, это избыточно.
Даниэль Бек
-1безусловно, излишним. Даже если lsне будет печатать один элемент в строке, когда он будет передан по конвейеру, это не повлияет на идею проверки, выдало ли оно ноль или более строк.
Виктор Ярема
3

Используйте следующее:

count="$( find /path -mindepth 1 -maxdepth 1 | wc -l )"
if [ $count -eq 0 ] ; then
   echo "No new file"
   exit 1
fi

Таким образом, вы не зависите от формата вывода ls. -mindepthпропускает сам каталог, -maxdepthпредотвращает рекурсивную защиту в подкаталогах для ускорения работы.

Даниэль Бек
источник
Конечно, вы теперь зависит wc -lи findформат вывода (который является достаточно простым , хотя).
Даниэль Бек
3

Используя массив:

files=( * .* )
if (( ${#files[@]} == 2 )); then
    # contents of files array is (. ..)
    echo dir is empty
fi
Гленн Джекман
источник
3
Очень хорошее решение, но обратите внимание, что оно требуетshopt -s nullglob
xebeche
3
${#files[@]} == 2Предположение не выдерживает для корневого каталога (вы, вероятно , не проверить , если он пустой , но некоторый код , который не знает о том , что ограничения могли бы).
ivan_pozdeev
3

Хакерский, но бездарный, без PID способ:

is_empty() {
    test -e "$1/"* 2>/dev/null
    case $? in
        1)   return 0 ;;
        *)   return 1 ;;
    esac
}

Это использует тот факт, что testвстроенная команда завершается с 2, если задано более одного аргумента после -e: во-первых, "$1"/*glob расширяется с помощью bash. Это приводит к одному аргументу на файл. Так

  • Если файлов нет, звездочка в test -e "$1"*не расширяется, поэтому Shell возвращается к попытке файла с именем *, который возвращает 1.

  • ... за исключением случаев, когда на самом деле существует один файл с точным именем *, звездочка расширяется до нуля, звездочка, которая заканчивается тем же вызовом, что и выше, т.е. test -e "dir/*", только на этот раз возвращает 0. (Спасибо @TrueY за указание на это.)

  • Если есть один файл, test -e "dir/file"запускается, который возвращает 0.

  • Но если файлов больше, чем 1, test -e "dir/file1" "dir/file2" запускается, и bash сообщает об этом как об ошибке использования, то есть 2.

case оборачивает всю логику так, что только первый случай с 1 состоянием выхода считается успешным.

Возможные проблемы, которые я не проверял:

  • Есть больше файлов, чем количество разрешенных аргументов - я думаю, это может вести себя подобно случаю с 2+ файлами.

  • Или на самом деле есть файл с пустым именем - я не уверен, что это возможно на любой вменяемой OS / FS.

Алоис Махдал
источник
1
Незначительное исправление: если в dir / нет файла, то test -e dir/*вызывается. Если в dir единственным файлом является '*', то test вернет 0. Если файлов больше, то он вернет 2. Таким образом, он работает как описано.
Правда
Вы правы, @TrueY, я включил это в ответ. Спасибо!
Алоис Махдал
2

С FIND (1) (в Linux и FreeBSD) вы можете нерекурсивно просматривать запись каталога с помощью "-maxdepth 0" и проверять, является ли она пустой с помощью "-empty". Применительно к вопросу это дает:

if test -n "$(find ./ -maxdepth 0 -empty)" ; then
    echo "No new file"
    exit 1
fi
TimJ
источник
Это не может быть на 100% портативным, но это элегантно.
Крейг Рингер,
1

Я думаю, что лучшее решение:

files=$(shopt -s nullglob; shopt -s dotglob; echo /MYPATH/*)
[[ "$files" ]] || echo "dir empty" 

спасибо https://stackoverflow.com/a/91558/520567

Это анонимное редактирование моего ответа, которое может кому-то пригодиться или не пригодиться. Небольшое изменение дает количество файлов:

files=$(shopt -s nullglob dotglob; s=(MYPATH/*); echo ${s[*]}) 
echo "MYPATH contains $files files"

Это будет работать правильно, даже если имена файлов содержат пробелы.

akostadinov
источник
1
if find "${DIR}" -prune ! -empty -exit 1; then
    echo Empty
else
    echo Not Empty
fi

РЕДАКТИРОВАТЬ: Я думаю, что это решение отлично работает с GNU Find, после быстрого взгляда на реализацию . Но это может не работать, например, с поиском netbsd . Действительно, он использует поле st_size stat (2). Руководство описывает это как:

st_size            The size of the file in bytes.  The meaning of the size
                   reported for a directory is file system dependent.
                   Some file systems (e.g. FFS) return the total size used
                   for the directory metadata, possibly including free
                   slots; others (notably ZFS) return the number of
                   entries in the directory.  Some may also return other
                   things or always report zero.

Лучшее решение, также более простое:

if find "${DIR}" -mindepth 1 -exit 1; then
    echo Empty
else
    echo Not Empty
fi

Кроме того, -прень в 1-м решении бесполезна.

РЕДАКТИРОВАТЬ: нет-выход для gnu find .. вышеуказанное решение хорошо для поиска NetBSD. Для GNU find это должно работать:

if [ -z "`find \"${DIR}\" -mindepth 1 -exec echo notempty \; -quit`" ]; then
    echo Empty
else
    echo Not Empty
fi
Ярл
источник
find из GNU findutils 4.6.0 (последняя версия) не имеет -exitпредиката .
Деннис
0

Это все здорово - просто превратил его в скрипт, чтобы я мог проверять наличие пустых каталогов ниже текущего. Ниже следует поместить в файл с именем 'findempty', поместить в путь где-нибудь, чтобы bash мог найти его и затем запустить chmod 755. Я думаю, можно легко изменить ваши специфические потребности.

#!/bin/bash
if [ "$#" == "0" ]; then 
find . -maxdepth 1 -type d -exec findempty "{}"  \;
exit
fi

COUNT=`ls -1A "$*" | wc -l`
if [ "$COUNT" == "0" ]; then 
echo "$* : $COUNT"
fi
Уоррен Шерликер
источник
0

Это работает для меня, чтобы проверить и обработать файлы в каталоге ../IN, учитывая, что скрипт находится в ../Scriptкаталоге:

FileTotalCount=0

    for file in ../IN/*; do
    FileTotalCount=`expr $FileTotalCount + 1`
done

if test "$file" = "../IN/*"
then

    echo "EXITING: NO files available for processing in ../IN directory. "
    exit

else

  echo "Starting Process: Found ""$FileTotalCount"" files in ../IN directory for processing."

# Rest of the Code
Arijit
источник
0

Как насчет тестирования, если каталог существует и не пуст в одном операторе if?

if [[ -d path/to/dir && -n "$(ls -A path/to/dir)" ]]; then 
  echo "directory exists"
else
  echo "directory doesn't exist"
fi
stanieviv
источник
-1

Для любого каталога, кроме текущего, вы можете проверить, является ли он пустым, попытавшись сделать rmdirэто, потому что rmdirгарантированно произойдет сбой для непустых каталогов. Если все rmdirполучилось, и вы действительно хотели, чтобы пустой каталог выдержал тест, просто mkdirснова.

Не используйте этот хак, если есть другие процессы, которые могут стать некомпетентными из-за каталога, который, как они знают, ненадолго прекратит свое существование.

Если у rmdirвас не получится, и вы можете тестировать каталоги, которые могут содержать большое количество файлов, любое решение, основанное на глобализации оболочки, может работать медленно и / или работать с ограничениями длины командной строки. Вероятно, лучше использовать findв этом случае. Самое быстрое findрешение, которое я могу придумать, выглядит как

is_empty() {
    test -z $(find "$1" -mindepth 1 -printf X -quit)
}

Это работает для версий GNU и BSD, findно не для версий Solaris, в которой отсутствует каждый из этих findоператоров. Люблю свою работу, Оракул.

flabdablet
источник
Не хорошая идея. ОП просто хотел проверить, был ли каталог пустым или нет.
Ройма
-3

Вы можете попытаться удалить каталог и дождаться ошибки; rmdir не удалит каталог, если он не пуст.

_path="some/path"
if rmdir $_path >/dev/null 2>&1; then
   mkdir $_path        # create it again
   echo "Empty"
else
   echo "Not empty or doesn't exist"
fi
impxd
источник
3
-1 Это тот код, который имеет неприятные последствия. rmdirпотерпит неудачу, если у меня нет разрешения на удаление каталога; или если это Btrfs subvolume; или если он принадлежит файловой системе только для чтения. А если rmdirне mkdirвыходит из строя и запускается: что, если уже удаленный каталог принадлежит другому пользователю? как насчет его (возможно, нестандартных) разрешений? ACL? расширенные атрибуты? Все потеряно.
Камиль Макиоровский
Ну, я только изучаю bash, и я подумал, что это может быть более быстрый способ, чем перебирать весь каталог, но процессоры мощные, а вы правы, не защищены.
августа