So überprüfen Sie, ob das Verzeichnis leer ist

12

Ich habe eine Anforderung, wenn ich ein Skript ./123mit Argumenten des leeren Pfads ausführe , sagen wir /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(dieses Verzeichnis ist leer). Es sollte "Verzeichnis ist leer" angezeigt werden.

Mein Code lautet:

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

Ich habe eine Fehleranzeige wie, wenn ich den Skriptnamen ausführe ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(dieses Verzeichnis ist leer).

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

Anstatt nur die Ausgabe "Verzeichnis ist leer" anzuzeigen, wird die obige Ausgabe angezeigt

Die folgende Ausgabe muss angezeigt werden, wenn ich das Skript mit korrekten Argumenten ausführe (ich meine mit korrektem Verzeichnispfad). sagen./123 /usr/share

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

Meine erwartete Ausgabe ist: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.
Buddha Sreekanth
quelle
4
Siehe mywiki.wooledge.org/BashFAQ/004
Valentin Bajrami
Kann jemand bitte meinen Code einmal überprüfen. Ich habe es bearbeitet
Buddha Sreekanth
Verwandte: superuser.com/questions/352289/...
AlikElzin-Kilaka

Antworten:

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

Das Obige ist ein POSIX-kompatibler Test - und sollte sehr schnell sein. lslistet alle Dateien / Verzeichnisse in einem Verzeichnis mit Ausnahme von .und ..jede pro Zeile auf und -qmarkiert alle nicht druckbaren Zeichen (einschließlich \nEwlines) in der Ausgabe mit einem ?Fragezeichen. Auf diese Weise wird, wenn grepnur ein einziges Zeichen in der Eingabe empfangen wird, true zurückgegeben - andernfalls false.

So machen Sie es allein in einer POSIX-Shell:

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"

Eine POSIX-Shell (die die -fGenerierung von Dateinamen zuvor nicht deaktiviert hat) führt setdas "$@"Positionsparameter-Array entweder zu den Literalzeichenfolgen, denen der setobige Befehl folgt , oder zu den Feldern, die am Ende jeweils von Glob-Operatoren generiert werden. Ob dies der Fall ist, hängt davon ab, ob die Globs tatsächlich zu irgendetwas passen. In einigen Shells können Sie einen nicht auflösenden Glob anweisen, auf Null zu expandieren - oder gar nichts. Dies kann manchmal von Vorteil sein, ist jedoch nicht portabel und bringt häufig zusätzliche Probleme mit sich - beispielsweise das Festlegen spezieller Shell-Optionen und das anschließende Deaktivieren dieser Optionen.

Das einzige tragbare Mittel zur Behandlung von Argumenten mit Nullwerten sind entweder leere oder nicht gesetzte Variablen oder ~Tilde-Erweiterungen. Und das letztere ist übrigens weitaus sicherer als das erstere.

Über der Shell wird nur eine der Dateien auf -eXistenz getestet, wenn keiner der drei angegebenen Globs in mehr als eine einzelne Übereinstimmung aufgelöst wird. Die forSchleife wird also immer nur für drei oder weniger Iterationen ausgeführt und nur im Fall eines leeren Verzeichnisses oder im Fall, dass eines oder mehrere der Muster nur in eine einzelne Datei aufgelöst werden. Das forauch, breakwenn einer der Globs eine tatsächliche Datei darstellt - und da ich die Globs in der Reihenfolge der wahrscheinlichsten bis unwahrscheinlichsten angeordnet habe, sollte sie jedes Mal bei der ersten Iteration so gut wie beendet werden.

In beiden Fällen sollte es sich nur um einen einzigen Systemaufruf handeln stat()- die Shell - und lsbeide müssen nur stat()das abgefragte Verzeichnis benötigen und die Dateien auflisten , die die darin enthaltenen Einträge enthalten. Dem steht das Verhalten gegenüber, finddas stattdessen stat()jede Datei enthält, die Sie möglicherweise auflisten.

mikeserv
quelle
Kann bestätigen. Ich verwende das erste Beispiel von mikeserv in Skripten, die ich geschrieben habe.
Otheus
Das würde als leere Verzeichnisse gemeldet, die nicht existieren oder für die Sie keinen Lesezugriff haben. Für den zweiten Fall, wenn Sie Lesezugriff, aber keinen Suchzugriff haben, YMMV.
Stéphane Chazelas
@ StéphaneChazelas - vielleicht, aber solche Berichte werden von Fehlermeldungen begleitet, um den Benutzer darüber zu informieren.
Mikeserv
1
Nur in dem lsFall. Globs und [schweigen, wenn sie keinen Zugriff haben. Zum Beispiel [ -e /var/spool/cron/crontabs/stephane ]wird stillschweigend false gemeldet, während eine Datei mit diesem Namen vorhanden ist.
Stéphane Chazelas
Beachten Sie auch, dass diese Lösungen nicht gleichwertig sind, wenn somedir ein Symlink zu einem Verzeichnis (oder nicht zu einem Verzeichnis) ist.
Stéphane Chazelas
6

Mit GNU oder modernen BSDs findkönnen Sie Folgendes tun:

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

(geht davon aus $dirsieht nicht wie ein findPrädikat ( !, (, -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
quelle
4

[-z $dir ]beschwert sich, dass [-zauf den meisten Systemen kein Befehl aufgerufen wird. Sie benötigen Leerzeichen um die Klammern .

[ -z $dir ]geschieht um wahr zu sein , wenn dirleer ist, und für die meisten anderen Werte falsch ist dir, aber es ist unzuverlässig, zum Beispiel es ist wahr , wenn der Wert dirist = -zoder -o -o -n -n. Verwenden Sie bei Befehlsersetzungen immer doppelte Anführungszeichen (dies gilt auch für den Rest Ihres Skripts).

[ -z "$dir" ]testet, ob der Wert der Variablen dirleer ist. Der Wert der Variablen ist eine Zeichenfolge, die zufällig der Pfad zum Verzeichnis ist. Das sagt nichts über das Verzeichnis selbst aus.

Es gibt keinen Operator zum Testen, ob ein Verzeichnis leer ist, wie es für eine reguläre Datei der [ -s "$dir" ]Fall ist ( gilt für ein Verzeichnis, auch wenn es leer ist). Eine einfache Möglichkeit zu testen, ob ein Verzeichnis leer ist, besteht darin, seinen Inhalt aufzulisten. Wenn Sie leeren Text erhalten, ist das Verzeichnis leer.

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

Auf älteren Systemen, die nicht haben ls -A, können Sie verwenden ls -a, aber dann .und ..werden aufgelistet.

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

(Ziehen Sie die Zeile, mit ..der begonnen wird, nicht ein, da die Zeichenfolge nur eine neue Zeile, keine neue Zeile und zusätzliches Leerzeichen enthalten muss.)

Gilles 'SO - hör auf böse zu sein'
quelle
Könnten Sie bitte diesen Teil "$ (ls -A -" $ dir ")" erklären? Was macht $ in und außerhalb von Klammern?
Buddha Sreekanth
@buddhasreekanth $(…)ist Befehlsersetzung
Gilles '
@Giles "$ (ls -A -" $ dir ")" funktioniert nicht mit seinem Wurffehler.
Buddha Sreekanth
@buddhasreekanth Was ist der Fehler? Kopieren Einfügen. Sie können nicht erwarten, dass Ihnen jemand hilft, wenn Sie nur „nicht arbeiten“ sagen, ohne genau zu erklären, was Sie beobachten.
Gilles 'SO - hör auf böse zu sein'
@ Giles das ist der Fehler. Wenn ich mein Skript ./filestats ausgeführt habe, wird der Fehler ausgelöst. $ / Filestats / home / sreekanth / testdir / aki / schatz Mindestdateigröße: 4096 / home / sreekanth / testdir / aki / schatz Maximale Dateigröße: 4096 / home / sreekanth / testdir / aki / schatz durchschnittliche Dateigröße: 4 Verzeichnis ist leer. Anstatt "Verzeichnis ist leer" anzuzeigen. Es wird mit minimaler Größe, maximaler Größe und durchschnittlicher Größe angezeigt.
Buddha Sreekanth
3

Sie sehen sich die Attribute des Verzeichnisses selbst an.

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

Sie möchten zählen, wie viele Dateien sich dort befinden:

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

Der Test für "Verzeichnis ist leer" lautet also:

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

So was:

$ 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
quelle
Dadurch wird gemeldet, dass das Verzeichnis leer ist, wenn Sie keinen Lesezugriff darauf haben oder es nicht vorhanden ist.
Stéphane Chazelas
1

Meine Antwort lautet:

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

Es ist POSIX-kompatibel und nicht viel wichtiger als die Lösung, die das Verzeichnis auflistet und die Ausgabe an grep weiterleitet.

Verwendung:

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

Ich mag die Antwort /unix//a/202276/160204 , die ich wie folgt umschreibe:

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

Es listet das Verzeichnis auf und leitet das Ergebnis an grep weiter. Stattdessen schlage ich eine einfache Funktion vor, die auf Glob-Erweiterung und Vergleich basiert.

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

Diese Funktion ist kein Standard-POSIX und ruft eine Subshell mit auf $(). Ich erkläre diese einfache Funktion zuerst, damit wir die endgültige Lösung (siehe die Antwort von tldr oben) später besser verstehen können.

Erläuterung:

Die linke Seite (LHS) ist leer, wenn keine Erweiterung erfolgt. Dies ist der Fall, wenn das Verzeichnis leer ist. Die Option nullglob ist erforderlich, da andernfalls der Glob selbst das Ergebnis der Erweiterung ist, wenn keine Übereinstimmung vorliegt. (Wenn die RHS mit den Globs der LHS übereinstimmt, wenn das Verzeichnis leer ist, funktioniert dies nicht, da Fehlalarme auftreten, wenn ein LHS-Glob mit einer einzelnen Datei übereinstimmt, die als Glob selbst bezeichnet wird: Die *im Glob entspricht der Teilzeichenfolge *im Dateinamen. ) Der Klammerausdruck {,.[^.],..?}deckt versteckte Dateien ab, aber nicht ..oder ..

Da shopt -s nullglobes in $()(einer Subshell) ausgeführt wird, ändert es nichts an der nullglobOption der aktuellen Shell, was normalerweise eine gute Sache ist. Auf der anderen Seite ist es eine gute Idee, diese Option in Skripten festzulegen, da es fehleranfällig ist, wenn ein Glob etwas zurückgibt, wenn keine Übereinstimmung vorliegt. Man könnte also die Option nullglob am Anfang des Skripts setzen und sie wird in der Funktion nicht benötigt. Denken wir daran: Wir wollen eine Lösung, die mit der Option nullglob funktioniert.

Vorsichtsmaßnahmen:

Wenn wir keinen Lesezugriff auf das Verzeichnis haben, meldet die Funktion dasselbe, als ob es ein leeres Verzeichnis gäbe. Dies gilt auch für eine Funktion, die das Verzeichnis auflistet und die Ausgabe erfasst.

Der shopt -s nullglobBefehl ist kein Standard-POSIX.

Es verwendet die von erstellte Subshell $(). Es ist keine große Sache, aber es ist schön, wenn wir es vermeiden können.

Profi:

Nicht, dass es wirklich wichtig wäre, aber diese Funktion ist viermal schneller als die vorherige, gemessen an der CPU-Zeit, die im Kernel innerhalb des Prozesses verbracht wird.

Andere Lösungen:

Wir können das nicht POSIX entfernen shopt -s nullglobBefehl auf der LHS und die Zeichenfolge setzen "$1/* $1/.[^.]* $1/..?*"in der RHS und separat die Fehlalarme eliminieren , die auftreten , wenn wir nur Dateien mit dem Namen haben '*', .[^.]*oder ..?*in dem Verzeichnis:

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

Ohne den shopt -s nullglobBefehl ist es jetzt sinnvoll, die Unterschale zu entfernen, aber wir müssen vorsichtig sein, da wir die Wortteilung vermeiden und dennoch eine Glob-Erweiterung auf der LHS zulassen möchten. Insbesondere das Zitieren zur Vermeidung von Wortaufteilungen funktioniert nicht, da es auch die Glob-Erweiterung verhindert. Unsere Lösung besteht darin, die Globs separat zu betrachten:

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

Wir haben immer noch eine Wortaufteilung für den einzelnen Glob, aber jetzt ist es in Ordnung, da dies nur dann zu einem Fehler führt, wenn das Verzeichnis nicht leer ist. Wir haben 2> / dev / null hinzugefügt, um die Fehlermeldung zu verwerfen, wenn viele Dateien mit dem angegebenen Glob auf der LHS übereinstimmen.

Wir erinnern uns, dass wir eine Lösung wollen, die auch mit der Nullglob-Option funktioniert. Die obige Lösung schlägt mit der Option nullglob fehl, da bei einem leeren Verzeichnis auch die LHS leer sind. Glücklicherweise heißt es nie, dass das Verzeichnis leer ist, wenn dies nicht der Fall ist. Es kann nur nicht gesagt werden, dass es leer ist, wenn es ist. Daher können wir die Nullglob-Option separat verwalten. Wir können die Fälle [ "$1/"* = "" ]usw. nicht einfach hinzufügen, da diese als [ = "" ]usw. erweitert werden und syntaktisch falsch sind. Also verwenden wir [ "$1/"* "" = "" ]stattdessen etc. Wir müssen wieder die drei Fälle betrachten *, ..?*und .[^.]*die versteckten Dateien passen, aber nicht .und... Diese stören nicht, wenn wir die Option nullglob nicht haben, da sie auch nie sagen, dass sie leer ist, wenn sie nicht leer ist. Die endgültige vorgeschlagene Lösung lautet also:

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

Sicherheitsbedenken:

Erstellen Sie zwei Dateien rmund xin ein leeres Verzeichnis , und führen Sie *auf der Eingabeaufforderung. Der Glob *wird auf erweitert rm xund ausgeführt, um ihn zu entfernen x. Dies ist kein Sicherheitsbedenken, da sich in unserer Funktion die Globs dort befinden, wo die Erweiterungen nicht als Befehle, sondern als Argumente angesehen werden, genau wie in for f in *.

Dominic108
quelle
$(set -f; echo "$1"/*)scheint eine ziemlich komplizierte Art zu schreiben "$1/*". Und dies stimmt nicht mit versteckten Verzeichnissen oder Dateien überein.
Muru
Was ich meinte ist , dass es keine Dateinamen Expansion in "$1/*"( man beachte die Anführungszeichen enthalten *), so dass die set -fund Subshell für unnötig ist , dass insbesondere.
Muru
Es beantwortet eine andere Frage: ob das Verzeichnis nicht versteckte Dateien oder Verzeichnisse enthält. Es tut mir leid. Ich werde es gerne löschen.
Dominic108
Es gibt Möglichkeiten , zu versteckten Dateien anzupassen (siehe die komplizierten glob in mikeserv Antwort : ./* ./.[!.]* ./..?*). Vielleicht können Sie das einbauen, um die Funktion zu vervollständigen.
Muru
Ah! Ich werde mir das ansehen und lernen und vielleicht werden wir eine vollständige Antwort haben, die auf der globalen Expansion basiert!
Dominic108
0
#/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
quelle
1
Hallo und willkommen auf der Seite. Bitte posten Sie keine Antworten, die nur Code und keine Erklärung sind. Kommentieren Sie den Code oder erklären Sie auf andere Weise, was Sie tun. Übrigens gibt es keinen Grund zu verwenden echo, alles was Sie brauchen ist list="$somedir/*". Dies ist auch schwierig und fehleranfällig. Sie haben nicht erwähnt, dass das OP den Pfad des Zielverzeichnisses angeben soll, einschließlich beispielsweise des abschließenden Schrägstrichs.
Terdon
0

Hier ist eine andere einfache Möglichkeit, dies zu tun. Angenommen, D ist der vollständige Pfadname des Verzeichnisses, das Sie auf Leere testen möchten.

Dann

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

Das funktioniert weil

du -s D

hat als Ausgabe

size_of_D   D

awk entfernt das D und wenn die Größe 0 ist, ist das Verzeichnis leer.

Richard Gostanian
quelle