Мой ответ tldr:
function emptydir {
[ "$1/"* "" = "" ] 2> /dev/null &&
[ "$1/"..?* "" = "" ] 2> /dev/null &&
[ "$1/".[^.]* "" = "" ] 2> /dev/null ||
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Он совместим с POSIX и, что не имеет большого значения, обычно быстрее, чем решение, которое выводит каталог и передает вывод в grep.
Использование:
if emptydir adir
then
echo "nothing found"
else
echo "not empty"
fi
Мне нравится ответ https://unix.stackexchange.com/a/202276/160204 , который я переписываю как:
function emptydir {
! { ls -1qA "./$1/" | grep -q . ; }
}
Он перечисляет каталог и передает результат в grep. Вместо этого я предлагаю простую функцию, основанную на расширении и сравнении глобусов.
function emptydir {
[ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}
Эта функция не является стандартной POSIX и вызывает подоболочку с $()
. Сначала я объясню эту простую функцию, чтобы мы могли лучше понять окончательное решение (см. Ответ tldr выше) позже.
Объяснение:
Левая сторона (LHS) пуста, когда не происходит расширения, что является случаем, когда каталог пуст. Опция nullglob обязательна, потому что в противном случае, когда нет совпадения, сам шар является результатом расширения. (Наличие RHS, совпадающего с глобусами LHS, когда каталог пуст, не работает из-за ложных срабатываний, возникающих, когда глобус LHS соответствует одному файлу с именем самого глобуса: *
глобус соответствует подстроке *
в имени файла. ) Выражение скобки {,.[^.],..?}
охватывает скрытые файлы, но не ..
или .
.
Поскольку shopt -s nullglob
выполняется внутри $()
(подоболочка), это не меняет nullglob
опцию текущей оболочки, что обычно хорошо. С другой стороны, это хорошая идея, чтобы установить эту опцию в сценариях, потому что склонен к ошибке, когда глобус возвращает что-то, когда нет совпадения. Таким образом, можно установить опцию nullglob в начале скрипта, и она не понадобится в функции. Давайте помнить об этом: нам нужно решение, которое работает с опцией nullglob.
Предостережения:
Если у нас нет доступа для чтения к каталогу, функция выдает сообщение так же, как если бы был пустой каталог. Это относится также к функции, которая перечисляет каталог и grep выводит.
Команда shopt -s nullglob
не стандартная POSIX.
Он использует подоболочку, созданную $()
. Это не имеет большого значения, но хорошо, если мы можем избежать этого.
Pro:
Не то чтобы это действительно имеет значение, но эта функция в четыре раза быстрее, чем предыдущая, измеряемая количеством процессорного времени, проведенного в ядре внутри процесса.
Другие решения:
Мы можем удалить ноны POSIX shopt -s nullglob
команды на LHS и поместите строку "$1/* $1/.[^.]* $1/..?*"
в RHS и устранить отдельно ложные срабатывания , которые возникают , когда у нас есть только файлы с именем '*'
, .[^.]*
или ..?*
в каталоге:
function emptydir {
[ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
[ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}
Без shopt -s nullglob
команды теперь имеет смысл удалить подоболочку, но мы должны быть осторожны, потому что мы хотим избежать разбиения слов и все же разрешить расширение глобуса на LHS. В частности, цитирование во избежание разбиения слов не работает, поскольку оно также предотвращает расширение глобуса. Наше решение состоит в том, чтобы рассматривать шарики отдельно:
function emptydir {
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
У нас все еще есть разделение слов для отдельного глобуса, но теперь все нормально, потому что это приведет к ошибке, только если каталог не пуст. Мы добавили 2> / dev / null, чтобы отменить сообщение об ошибке, когда на LHS есть много файлов, соответствующих данному глобу.
Напомним, что нам нужно решение, которое работает и с опцией nullglob. Вышеупомянутое решение завершается ошибкой с параметром nullglob, потому что, когда каталог пуст, LHS также пуст. К счастью, это никогда не говорит, что каталог пуст, когда это не так. Он только не может сказать, что он пуст, когда он есть. Таким образом, мы можем управлять опцией nullglob отдельно. Мы не можем просто добавлять случаи [ "$1/"* = "" ]
и т. Д., Поскольку они будут расширяться как [ = "" ]
и т. Д., Которые синтаксически некорректны Таким образом, мы используем [ "$1/"* "" = "" ]
и т.д. вместо этого. Мы снова должны рассмотреть три случая *
, ..?*
и .[^.]*
в соответствии скрытые файлы, но не .
и..
, Они не будут мешать, если у нас нет опции nullglob, потому что они также никогда не говорят, что она пуста, когда ее нет. Итак, окончательное предлагаемое решение:
function emptydir {
[ "$1/"* "" = "" ] 2> /dev/null &&
[ "$1/"..?* "" = "" ] 2> /dev/null &&
[ "$1/".[^.]* "" = "" ] 2> /dev/null ||
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Проблема безопасности:
Создайте два файла rm
и x
в пустой директории и выполните *
в приглашении. Глобус *
расширится до rm x
и будет выполнен для удаления x
. Это не проблема безопасности, потому что в нашей функции глобусы расположены там, где расширения рассматриваются не как команды, а как аргументы, как в for f in *
.