como verificar o diretório está vazio

15

Eu tenho um requisito, se eu executar um script ./123com argumentos de caminho vazio, diga /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(este diretório está vazio). Ele deve exibir "o diretório está vazio"

Meu código é:

#!/bin/bash
dir="$1"

if [ $# -ne 1 ]
then
    echo "please pass arguments" 
exit
fi

if [ -e $dir ]
then
printf "minimum file size: %s\n\t%s\n" \
 $(du $dir -hab | sort -n -r | tail -1)

printf "maximum file size: %s\n\t%s\n" \
 $(du $dir -ab | sort -n | tail -1)

printf "average file size: %s"
du $dir -sk | awk '{s+=$1}END{print s/NR}'
else
   echo " directory doesn't exists"
fi

if [ -d "ls -A $dir" ]
 then
    echo " directory is  empty"
fi

Eu tenho um erro exibido como, se eu executar o nome do script ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(este diretório está vazio).

minimum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
maximum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
average file size: 4

em vez de mostrar apenas a saída "o diretório está vazio", mostra a saída acima

A saída abaixo deve ser exibida se eu exceder o script com argumentos corretos (quero dizer, com o caminho correto do diretório). dizer./123 /usr/share

minimum file size: 196
        /usr/share
    maximum file size: 14096
        /usr/share
    average file size: 4000

minha saída esperada é: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.
buddha sreekanth
fonte
4
Veja mywiki.wooledge.org/BashFAQ/004
Valentin Bajrami
alguém pode verificar o meu código uma vez. Eu editei
buddha sreekanth
Relacionados: superuser.com/questions/352289/...
AlikElzin-Kilaka

Respostas:

20
if    ls -1qA ./somedir/ | grep -q .
then  ! echo somedir is not empty
else  echo somedir is empty
fi

O acima é um teste compatível com POSIX - e deve ser muito rápido. lsirá listar todos os arquivos / diretórios em um diretório excetuando .e ..cada um por linha e -quote todos os caracteres não imprimíveis (para incluir \newlines) na saída com um ?ponto de interrogação. Dessa maneira, se grepreceber um único caractere na entrada, ele retornará verdadeiro - caso contrário, falso.

Para fazer isso apenas em um shell POSIX:

cd  ./somedir/ || exit
set ./* ./.[!.]* ./..?*
if   [ -n "$4" ] ||
     for e do 
         [ -L "$e" ] ||
         [ -e "$e" ] && break
     done
then ! echo somedir is not empty
else   echo somedir is empty
fi
cd "$OLDPWD"

Um shell POSIX (que não desativou anteriormente a -fgeração do nome do il) será seta "$@"matriz do parâmetro posicional para as seqüências literais seguidas pelo setcomando acima, ou para os campos gerados pelos operadores glob no final de cada. Se isso ocorre, depende se os globos realmente correspondem a alguma coisa. Em algumas conchas, você pode instruir um globo não resolvido a expandir para nulo - ou nada. Às vezes, isso pode ser benéfico, mas não é portátil e geralmente traz problemas adicionais - como ter que definir opções especiais do shell e depois desabilitá-las.

Os únicos meios portáteis de lidar com argumentos de valor nulo envolvem variáveis ​​vazias ou não definidas ou ~expansões de til. E o último, a propósito, é muito mais seguro que o primeiro.

Acima do shell, apenas testará qualquer um dos arquivos quanto à -eexistência de xistence se nenhum dos três globos especificados resolver mais do que uma única correspondência. Portanto, o forloop é executado apenas por três ou menos iterações e apenas no caso de um diretório vazio, ou no caso em que um ou mais padrões resolvem apenas para um único arquivo. O s fortambém breakse algum dos globs representar um arquivo real - e como eu organizei os globs na ordem do mais provável para o menos provável, ele deve sair praticamente na primeira iteração todas as vezes.

De qualquer forma, você deve envolver apenas uma stat()chamada do sistema - o shell e os lsdois devem apenas precisar stat()do diretório consultado e listar os arquivos que seus dentries relatam que ele contém. Esta é contrastada com o comportamento de findque, ao invés, stat()todos os arquivos que você pode listar com ele.

mikeserv
fonte
Pode confirmar. Eu uso o exemplo inicial do mikeserv nos scripts que escrevi.
Otheus
Isso reportaria como diretórios vazios que não existem ou para os quais você não tem acesso de leitura. Para o segundo, se você tiver acesso de leitura, mas não acesso de pesquisa, YMMV.
Stéphane Chazelas
@ StéphaneChazelas - talvez, mas esses relatórios serão acompanhados de mensagens de erro para notificar o usuário.
mikeserv
1
Somente no lscaso. globs e [ficam em silêncio quando não têm acesso. Por exemplo, [ -e /var/spool/cron/crontabs/stephane ]silenciosamente reportará false, enquanto houver um arquivo com esse nome.
Stéphane Chazelas
Observe também que essas soluções não são equivalentes se somedir for um link simbólico para um diretório (ou não).
Stéphane Chazelas
6

Com o GNU ou BSDs modernos find, você pode:

if find -- "$dir" -prune -type d -empty | grep -q .; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from find above."
fi

(assume $dirnão se parece com um findpredicado ( !, (, -name...).

POSIXly:

if [ -d "$dir" ] && files=$(ls -qAL -- "$dir") && [ -z "$files" ]; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from ls above."
fi
Stéphane Chazelas
fonte
4

[-z $dir ]reclama que não há nenhum comando chamado [-zna maioria dos sistemas. Você precisa de espaços ao redor dos colchetes .

[ -z $dir ]acontece que é verdadeiro se direstiver vazio e é falso para a maioria dos outros valores de dir, mas não é confiável, por exemplo, é verdadeiro se o valor de diré = -zou -o -o -n -n. Sempre use aspas duplas nas substituições de comandos (isso também vale para o resto do seu script).

[ -z "$dir" ]testa se o valor da variável direstá vazio. O valor da variável é uma sequência, que passa a ser o caminho para o diretório. Isso não diz nada sobre o diretório em si.

Não há operador para testar se um diretório está vazio, como existe para um arquivo regular ( [ -s "$dir" ]é verdade para um diretório mesmo que esteja vazio). Uma maneira simples de testar se um diretório está vazio é listar seu conteúdo; se você receber um texto vazio, o diretório estará vazio.

if [ -z "$(ls -A -- "$dir")" ]; then 

Em sistemas mais antigos que não possuem ls -A, você pode usar ls -a, mas depois .e ..são listados.

if [ -z "$(LC_ALL=C ls -a -- "$dir")" = ".
.." ]; then 

(Não recue a linha que começa com, ..pois a string deve conter apenas uma nova linha, não uma nova linha e espaço extra.)

Gilles 'SO- parar de ser mau'
fonte
você poderia explicar esta parte "$ (ls -A -" $ dir ")". o que $ dentro e fora dos colchetes faz?
Buddha sreekanth
@buddhasreekanth $(…)é substituição de comando
Gilles 'SO- stop
@Giles "$ (ls -A -" $ dir ")" não está funcionando com seu erro de lançamento.
Buddha sreekanth
@buddhasreekanth Qual é o erro? Copiar colar. Você não pode esperar que as pessoas o ajudem se você disser "não está funcionando" sem explicar exatamente o que observa.
Gilles 'SO- stop be evil'
@ Giles este é o erro. Quando executei meu script ./filestats, seu erro de lançamento .. $ / filestats / home / sreekanth / testdir / aki / schatz tamanho mínimo do arquivo: 4096 / home / sreekanth / testdir / aki / schatz tamanho máximo do arquivo: 4096 / home / sreekanth / testdir / aki / schatz tamanho médio do arquivo: 4 o diretório está vazio. Em vez de mostrar "o diretório está vazio". É exibido com tamanho mínimo, tamanho máximo e tamanho médio.
Buddha sreekanth
3

Você está olhando para os atributos do próprio diretório.

$ mkdir /tmp/foo
$ ls -ld /tmp/foo
drwxr-xr-x 2 jackman jackman 4096 May  8 11:32 /tmp/foo
# ...........................^^^^

Você quer contar quantos arquivos estão lá:

$ dir=/tmp/foo
$ shopt -s nullglob
$ files=( "$dir"/* "$dir"/.* )
$ echo ${#files[@]}
2
$ printf "%s\n" "${files[@]}"
/tmp/foo/.
/tmp/foo/..

Portanto, o teste para "o diretório está vazio" é:

function is_empty {
    local dir="$1"
    shopt -s nullglob
    local files=( "$dir"/* "$dir"/.* )
    [[ ${#files[@]} -eq 2 ]]
}

Como isso:

$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
it's empty
$ touch /tmp/foo/afile
$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
not empty
Glenn Jackman
fonte
Isso informará que o diretório está vazio se você não tiver acesso de leitura ou ele não existir.
Stéphane Chazelas
1

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 nullglobopçã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 nullglobcomando 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 nullglobcomando 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 nullglobcomando, 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 rme xem um diretório vazio e execute *no prompt. A glob *expandirá para rm xe 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 *.

Dominic108
fonte
$(set -f; echo "$1"/*)parece uma maneira bastante complicada de escrever "$1/*". E isso não corresponde a diretórios ou arquivos ocultos.
Muru
O que eu quis dizer é que não há expansão de nome de arquivo "$1/*"(observe as aspas *), portanto, o set -fsubshell e é desnecessário para isso em particular.
muru 11/12/19
Ele responde a uma pergunta diferente: se o diretório contém arquivos ou diretórios não ocultos. Me desculpe sobre isso. Ficarei feliz em excluí-lo.
Dominic108
1
Existem maneiras para combinar arquivos ocultos também (ver o glob complicado na resposta de mikeserv : ./* ./.[!.]* ./..?*). Talvez você possa incorporar isso para completar a função.
muru 11/12/19
Ah! Vou olhar para isso e aprender e talvez tenhamos uma resposta completa com base na expansão globbing!
Dominic108
0

Aqui está outra maneira simples de fazer isso. Suponha que D seja o nome do caminho completo do diretório que você deseja testar para verificar o vazio.

Então

if [[ $( du -s  D |awk ' {print $1}') = 0 ]]
then
      echo D is empty
fi

Isso funciona porque

du -s D

tem como saída

size_of_D   D

O awk remove o D e se o tamanho for 0, o diretório está vazio.

Richard Gostanian
fonte
-1
#/bin/sh

somedir=somedir
list="`echo "$somedir"/*`"

test "$list" = "$somedir/*" && echo "$somedir is empty"

# dot prefixed entries would be skipped though, you have to explicitly check them
user137815
fonte
2
Olá e bem-vindo ao site! Por favor, não poste respostas que sejam apenas código e nenhuma explicação. Comente o código ou explique o que você está fazendo. A propósito, não há razão para usar echo, tudo o que você precisa é list="$somedir/*". Além disso, isso é complicado e propenso a erros. Você não mencionou que o OP deve fornecer o caminho do diretório de destino, incluindo a barra final, por exemplo.
terdon