Minha resposta 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/..?*" ]
}
É compatível com POSIX e, não importa muito, geralmente é mais rápido que a solução que lista o diretório e canaliza a saída para grep.
Uso:
if emptydir adir
then
echo "nothing found"
else
echo "not empty"
fi
Gosto da resposta /unix//a/202276/160204 , que reescrevo como:
function emptydir {
! { ls -1qA "./$1/" | grep -q . ; }
}
Ele lista o diretório e canaliza o resultado para grep. Em vez disso, proponho uma função simples baseada na expansão e comparação de globos.
function emptydir {
[ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}
Esta função não é padrão POSIX e chama um subshell com $()
. Eu explico esta função simples primeiro para que possamos entender melhor a solução final (veja a resposta tldr acima) mais tarde.
Explicação:
O lado esquerdo (LHS) fica vazio quando não ocorre expansão, o que ocorre quando o diretório está vazio. A opção nullglob é necessária porque, caso contrário, quando não houver correspondência, o glob em si é o resultado da expansão. (O fato de o RHS corresponder aos globos do LHS quando o diretório está vazio não funciona devido a falsos positivos que ocorrem quando um glob LHS corresponde a um único arquivo chamado como o próprio globo: *
o glob corresponde ao substring *
no nome do arquivo. ) A expressão entre chaves {,.[^.],..?}
abrange arquivos ocultos, mas não ..
ou .
.
Como shopt -s nullglob
é executado dentro $()
(um subshell), ele não altera a nullglob
opção do shell atual, o que normalmente é uma coisa boa. Por outro lado, é uma boa ideia definir essa opção nos scripts, porque é propenso a erros que um globo retorne algo quando não há correspondência. Portanto, pode-se definir a opção nullglob no início do script e isso não será necessário na função. Vamos ter isso em mente: queremos uma solução que funcione com a opção nullglob.
Ressalvas:
Se não tivermos acesso de leitura ao diretório, a função reportará o mesmo como se houvesse um diretório vazio. Isso se aplica também a uma função que lista o diretório e grep a saída.
O shopt -s nullglob
comando não é padrão POSIX.
Ele usa o subshell criado por $()
. Não é grande coisa, mas é bom se pudermos evitar.
Pró:
Não que isso realmente importe, mas essa função é quatro vezes mais rápida que a anterior, medida com a quantidade de tempo de CPU gasto no kernel no processo.
Outras soluções:
Nós podemos remover o não POSIX shopt -s nullglob
comando no LHS e colocar a corda "$1/* $1/.[^.]* $1/..?*"
no RHS e eliminar separadamente os falsos positivos que ocorrem quando temos apenas arquivos nomeados '*'
, .[^.]*
ou ..?*
no diretório:
function emptydir {
[ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
[ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}
Sem o shopt -s nullglob
comando, agora faz sentido remover o subshell, mas precisamos ter cuidado, pois queremos evitar a divisão de palavras e, no entanto, permitir a expansão global no LHS. Em particular, citar para evitar a divisão de palavras não funciona, porque também impede a expansão glob. Nossa solução é considerar os globs separadamente:
function emptydir {
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Ainda temos a divisão de palavras para o globo individual, mas tudo bem agora, porque isso só resultará em erro quando o diretório não estiver vazio. Adicionamos 2> / dev / null, para descartar a mensagem de erro quando houver muitos arquivos correspondentes ao glob fornecido no LHS.
Lembramos que queremos uma solução que funcione também com a opção nullglob. A solução acima falha com a opção nullglob, porque quando o diretório está vazio, o LHS também está vazio. Felizmente, nunca diz que o diretório está vazio quando não está. Ele apenas falha ao dizer que está vazio quando está. Portanto, podemos gerenciar a opção nullglob separadamente. Não podemos simplesmente adicionar os casos, [ "$1/"* = "" ]
etc., porque eles serão expandidos como [ = "" ]
, etc., que são sintaticamente incorretos. Então, usamos [ "$1/"* "" = "" ]
etc. em vez disso. Temos novamente a considerar os três casos *
, ..?*
e .[^.]*
para combinar os arquivos ocultos, mas não .
e..
. Isso não interferirá se não tivermos a opção nullglob, porque eles também nunca dizem que ela está vazia quando não está. Portanto, a solução final proposta é:
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/..?*" ]
}
Preocupação com segurança:
Crie dois arquivos rm
e x
em um diretório vazio e execute *
no prompt. A glob *
expandirá para rm x
e isso será executado para remover x
. Isso não é uma preocupação de segurança, porque, em nossa função, os globs estão localizados onde as expansões não são vistas como comandos, mas como argumentos, assim como em for f in *
.