come controllare la directory è vuota


15

Ho un requisito, se eseguo uno script ./123con argomenti di percorso vuoto, dire /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(questa directory è vuota). Dovrebbe essere visualizzato "directory vuota"

Il mio codice è:

#!/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

Ho visualizzato un errore simile, se eseguo il nome dello script ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(questa directory è vuota).

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

invece di mostrare solo l'output "la directory è vuota" mostra l'output sopra

L'output seguente deve essere visualizzato se eseguo lo script con argomenti corretti (intendo con il percorso di directory corretto). dire./123 /usr/share

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

la mia uscita prevista è: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.


qualcuno può controllare il mio codice una volta. L'ho modificato
buddha sreekanth

Risposte:


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

Quanto sopra è un test compatibile con POSIX - e dovrebbe essere molto veloce. lselencherà tutti i file / le directory in una directory eccetto .e ..ciascuno per riga e -qincluderà tutti i caratteri non stampabili (per includere le \newline) nell'output con un punto ?interrogativo. In questo modo se grepriceve anche un solo carattere in input, restituirà true - altrimenti false.

Per farlo in una sola 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"

Una shell POSIX (che non ha precedentemente disabilitato la -fgenerazione di ilename) eseguirà setl' "$@"array di parametri posizionali sulle stringhe letterali seguite dal setcomando sopra, oppure sui campi generati dagli operatori glob alla fine di ciascuna. Se lo fa dipende dal fatto che i globs corrispondano effettivamente a qualcosa. In alcune shell è possibile indicare a un glob non risolvibile di espandersi fino a null - o niente del tutto. Questo a volte può essere utile, ma non è portatile e spesso presenta problemi aggiuntivi, come la necessità di impostare opzioni di shell speciali e successivamente annullarle.

L'unico mezzo portatile per gestire argomenti a valore nullo comporta variabili vuote o non impostate o ~espansioni di tilde. E il secondo, a proposito, è molto più sicuro del primo.

Sopra la shell viene testato uno qualsiasi dei file per la -existence se nessuno dei tre globs specificati si risolve in più di un singolo match. Quindi il forciclo viene eseguito per sempre solo per tre o meno iterazioni, e solo nel caso di una directory vuota o nel caso in cui uno o più dei pattern si risolvano in un singolo file. Il foranche breaks se una qualsiasi delle gocce rappresentano un file vero e proprio - e come ho sistemato le globs nell'ordine del più probabile al meno probabile, si deve praticamente uscire alla prima iterazione ogni volta.

In entrambi i casi, ciò dovrebbe comportare solo una singola stat()chiamata di sistema : la shell ed lsentrambi dovrebbero solo avere bisogno stat()della directory interrogata ed elencare i file che i suoi rapporti dentistici contengono. Ciò è contrastato dal comportamento di findcui invece sarebbe stat()ogni file che potresti elencare con esso.


Posso confermare Uso l'esempio iniziale di mikeserv negli script che ho scritto.
Otheus,

Ciò significherebbe directory vuote che non esistono o per le quali non hai accesso in lettura. Per il secondo, se si dispone dell'accesso in lettura ma non dell'accesso alla ricerca, YMMV.
Stéphane Chazelas,

@ StéphaneChazelas - forse, ma tali rapporti saranno accompagnati da messaggi di errore per avvisare l'utente di tale.
Mikeserv,

1
Solo nel lscaso. rimbalza e [tace quando non ha accesso. Ad esempio, [ -e /var/spool/cron/crontabs/stephane ]segnalerà silenziosamente false, mentre esiste un file con quel nome.
Stéphane Chazelas,

Si noti inoltre che tali soluzioni non sono equivalenti se somedir è un collegamento simbolico a una directory (o non a una directory).
Stéphane Chazelas,

6

Con GNU o BSD moderni find, puoi fare:

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

(presuppone $dirnon sembra come un findpredicato ( !, (, -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

4

[-z $dir ]si lamenta che non esiste alcun comando chiamato [-zsulla maggior parte dei sistemi. Hai bisogno di spazi attorno alle parentesi .

[ -z $dir ]sembra essere vero se dirè vuoto ed è falso per la maggior parte degli altri valori di dir, ma non è affidabile, ad esempio è vero se il valore di dirè = -zo -o -o -n -n. Usa sempre le doppie virgolette intorno alle sostituzioni di comandi (questo vale anche per il resto dello script).

[ -z "$dir" ]verifica se il valore della variabile dirè vuoto. Il valore della variabile è una stringa, che risulta essere il percorso della directory. Questo non ti dice nulla sulla directory stessa.

Non esiste un operatore per verificare se una directory è vuota, come nel caso di un file normale ( [ -s "$dir" ]vale per una directory anche se è vuota). Un modo semplice per verificare se una directory è vuota è elencarne il contenuto; se si ottiene un testo vuoto, la directory è vuota.

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

Su sistemi più vecchi che non hanno ls -A, è possibile utilizzare ls -a, ma quindi .e ..sono elencati.

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

(Non rientrare nella riga che inizia ..poiché la stringa deve contenere solo una nuova riga, non una nuova riga e spazio aggiuntivo.)


potresti per favore spiegare questa parte "$ (ls -A -" $ dir ")". cosa fa $ dentro e fuori dalle parentesi?
buddha sreekanth,


@Giles "$ (ls -A -" $ dir ")" non funziona con il suo errore di lancio.
buddha sreekanth,

@buddhasreekanth Qual è l'errore? Copia incolla. Non puoi aspettarti che le persone ti aiutino se dici semplicemente di "non lavorare" senza spiegare esattamente cosa osservi.
Gilles 'SO- smetti di essere malvagio'

@Giles questo è l'errore. Quando ho eseguito il mio script ./filestats il suo errore di lancio .. $ / filestats / home / sreekanth / testdir / aki / schatz dimensione minima del file: 4096 / home / sreekanth / testdir / aki / schatz dimensione massima del file: 4096 / home / sreekanth / testdir / aki / schatz dimensione media file: 4 directory vuota. Invece di mostrare "la directory è vuota". Viene visualizzato con dimensioni minime, dimensioni massime e dimensioni medie.
buddha sreekanth,

3

Stai guardando gli attributi della directory stessa.

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

Vuoi contare quanti file ci sono:

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

Quindi, il test per "directory è vuota" è:

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

Come questo:

$ 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

Ciò segnalerà che la directory è vuota se non si ha accesso in lettura ad essa o se non esiste.
Stéphane Chazelas,

1

La mia risposta 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/..?*" ]
}

È conforme a POSIX e, non importa molto, in genere è più veloce della soluzione che elenca la directory e reindirizza l'output a grep.

Uso:

if emptydir adir
then
  echo "nothing found" 
else 
  echo "not empty" 
fi

Mi piace la risposta https://unix.stackexchange.com/a/202276/160204 , che riscrivo come:

function emptydir {
  ! { ls -1qA "./$1/" | grep -q . ; }
}

Elenca la directory e reindirizza il risultato a grep. Invece, propongo una semplice funzione che si basa sull'espansione e sul confronto globale.

function emptydir {   
 [ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}

Questa funzione non è POSIX standard e chiama una subshell con $(). Spiego prima questa semplice funzione in modo che possiamo capire meglio la soluzione finale (vedi la risposta tldr sopra) in seguito.

Spiegazione:

Il lato sinistro (LHS) è vuoto quando non si verifica alcuna espansione, come nel caso della directory vuota. L'opzione nullglob è obbligatoria perché altrimenti quando non vi è corrispondenza, il glob stesso è il risultato dell'espansione. (Avere l'RHS corrispondente ai globs dell'LHS quando la directory è vuota non funziona a causa dei falsi positivi che si verificano quando un glob LHS corrisponde a un singolo file chiamato glob stesso: il *nel glob corrisponde alla sottostringa *nel nome del file. ) L'espressione parentesi graffa {,.[^.],..?}copre i file nascosti, ma non ..oppure ..

Poiché shopt -s nullglobviene eseguito all'interno $()(una subshell), non cambia l' nullglobopzione della shell corrente, che di solito è una buona cosa. D'altra parte, è una buona idea impostare questa opzione negli script, perché è soggetto a errori che un glob restituisca qualcosa quando non c'è corrispondenza. Quindi, si potrebbe impostare l'opzione nullglob all'inizio dello script e non sarà necessario nella funzione. Ricordiamolo: vogliamo una soluzione che funzioni con l'opzione nullglob.

Avvertenze:

Se non abbiamo accesso in lettura alla directory, la funzione riporta lo stesso di una directory vuota. Questo vale anche per una funzione che elenca la directory e grep l'output.

Il shopt -s nullglobcomando non è POSIX standard.

Utilizza la subshell creata da $(). Non è un grosso problema, ma è bello se possiamo evitarlo.

Pro:

Non che sia davvero importante, ma questa funzione è quattro volte più veloce della precedente, misurata con la quantità di tempo della CPU speso nel kernel all'interno del processo.

Altre soluzioni:

Siamo in grado di rimuovere il non POSIX shopt -s nullglobdei comandi sul lato sinistro e inserire la stringa "$1/* $1/.[^.]* $1/..?*"nel RHS ed eliminare separatamente i falsi positivi che si verificano quando abbiamo solo i file denominati '*', .[^.]*o ..?*nella directory:

function emptydir {
 [ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
 [ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}

Senza il shopt -s nullglobcomando, ora ha senso rimuovere la subshell, ma dobbiamo stare attenti perché vogliamo evitare la divisione delle parole e tuttavia consentire l'espansione globale su LHS. In particolare, la citazione per evitare la divisione delle parole non funziona, perché impedisce anche l'espansione globale. La nostra soluzione è considerare i globs separatamente:

function emptydir {
 [ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}

Abbiamo ancora una suddivisione delle parole per il singolo glob, ma ora va bene, perché si tradurrà in un errore solo quando la directory non è vuota. Abbiamo aggiunto 2> / dev / null, per scartare il messaggio di errore quando ci sono molti file corrispondenti al glob dato sull'LHS.

Ricordiamo che vogliamo una soluzione che funzioni anche con l'opzione nullglob. La soluzione precedente non riesce con l'opzione nullglob, perché quando la directory è vuota, anche l'LHS è vuoto. Fortunatamente, non dice mai che la directory è vuota quando non lo è. Non riesce a dire che è vuoto quando lo è. Quindi, possiamo gestire l'opzione nullglob separatamente. Non possiamo semplicemente aggiungere i casi [ "$1/"* = "" ]ecc. Perché questi si espandono come [ = "" ], ecc. Che sono sintatticamente errati. Quindi, [ "$1/"* "" = "" ]invece usiamo ecc. Abbiamo ancora una volta considerare i tre casi *, ..?*e .[^.]*di abbinare i file nascosti, ma non .e... Questi non interferiranno se non abbiamo l'opzione nullglob, perché non dicono mai che è vuota quando non lo è. Quindi, la soluzione finale 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/..?*" ]
}

Problemi di sicurezza:

Creare due file rme xin una directory vuota ed eseguire *sul prompt. Il glob *si espanderà rm xe questo verrà eseguito per rimuoverlo x. Questo non è un problema di sicurezza, perché nella nostra funzione, i globs si trovano in cui le espansioni non sono viste come comandi, ma come argomenti, proprio come in for f in *.


$(set -f; echo "$1"/*)sembra un modo piuttosto complicato di scrivere "$1/*". E questo non corrisponderà a directory o file nascosti.
Muru,

Quello che volevo dire è che non v'è alcuna espansione dei nomi a "$1/*"(da notare le quote comprendono *), in modo che il set -fe subshell sono inutili per quel in particolare.
Muru,

Risponde a una domanda diversa: se la directory contiene file o directory non nascosti. Mi dispiace per questo. Sarò felice di eliminarlo.
Dominic108,

1
Ci sono modi per abbinare i file nascosti troppo (vedi il glob complicato in risposta di mikeserv : ./* ./.[!.]* ./..?*). Forse puoi incorporarlo per completare la funzione.
Muru,

Ah! Lo guarderò e imparerò e forse avremo una risposta completa basata su un'espansione sconvolgente!
Dominic108,

0

Ecco un altro modo semplice per farlo. Supponiamo che D sia il nome del percorso completo della directory che si desidera verificare per il vuoto.

Poi

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

Questo funziona perché

du -s D

ha come output

size_of_D   D

awk rimuove la D e se la dimensione è 0 la directory è vuota.


-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

2
Ciao e benvenuto nel sito. Si prega di non pubblicare risposte che sono solo codice e nessuna spiegazione. Commenta il codice o spiega in altro modo cosa stai facendo. A proposito, non c'è motivo di usare echo, tutto ciò che serve è list="$somedir/*". Inoltre, questo è difficile e soggetto a errori. Non hai menzionato che l'OP dovrebbe fornire il percorso della directory di destinazione, inclusa la barra finale, per esempio.
terdon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.