Mit Bash and Dash können Sie nur mit der Shell nach einem leeren Verzeichnis suchen (Punktdateien ignorieren, um die Dinge einfach zu halten):
set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi
Allerdings habe ich kürzlich erfahren, dass Zsh in diesem Fall spektakulär versagt:
% set *
zsh: no matches found: *
% echo "$? $#"
1 0
Der set
Befehl schlägt also nicht nur fehl, sondern wird auch nicht gesetzt $@
. Ich nehme an, ich könnte testen, ob dies der Fall $#
ist 0
, aber es scheint, dass Zsh die Ausführung sogar stoppt:
% { set *; echo 2; }
zsh: no matches found: *
Vergleiche mit Bash und Dash:
$ { set *; echo 2; }
2
Kann dies auf eine Weise geschehen, die in Bash, Dash und Zsh funktioniert?
Antworten:
Damit gibt es mehrere Probleme
Code:
nullglob
Option (abzsh
jetzt aber von den meisten anderen Shells unterstützt) aktiviert ist,set *
wird ,set
welche Listen Sie alle Shell - Variablen (und Funktionen in einigen Muscheln)-
oder beginnt+
, wird sie von als Option behandeltset
. Diese beiden Probleme können stattdessen behoben werdenset -- *
.*
Erweitert nur nicht versteckte Dateien. Es ist also kein Test, ob das Verzeichnis leer ist oder nicht, sondern ob es nicht versteckte Dateien enthält oder nicht. Mit einigen Muscheln und Sie können verwendendotglob
oderglobdot
Option oder das Spiel mit einerFIGNORE
speziellen variabel in Abhängigkeit von der Schale zu arbeiten um die.[ -e "$1" ]
Testet, ob einstat()
Systemaufruf erfolgreich ist oder nicht. Wenn die erste Datei ein Symlink zu einem unzugänglichen Speicherort ist, wird false zurückgegeben. Sie sollten keine Dateistat()
(nicht einmallstat()
) benötigen, um zu wissen, ob ein Verzeichnis leer ist oder nicht. Überprüfen Sie nur, ob es Inhalt enthält.*
Bei der Erweiterung wird das aktuelle Verzeichnis geöffnet, alle Einträge abgerufen, alle nicht ausgeblendeten gespeichert und sortiert, was ebenfalls ineffizient ist.Der effizienteste Weg, um zu überprüfen, ob ein Verzeichnis nicht leer ist (mit einem anderen Eintrag als
.
und..
),zsh
ist dasF
Glob-Qualifikationsmerkmal (F
für vollständig , hier nicht leer):N
ist dasnullglob
Glob-Qualifikationsmerkmal. Erweitert.(NF)
sich also,.
wenn.
voll ist und sonst nichts.Nachdem die
lstat()
auf dem Verzeichnis, wennzsh
es hat Funde eine Link-Zähler größer als 2 ist , dann das bedeutet , es hat zumindest ein Unterverzeichnis so leer ist , nicht, so dass wir dieses Verzeichnis nicht einmal öffnen müssen (das bedeutet auch, dass In diesem Fall können wir feststellen, dass das Verzeichnis nicht leer ist, auch wenn wir keinen Lesezugriff darauf haben. Andernfalls öffnet zsh das Verzeichnis, liest seinen Inhalt und stoppt beim ersten Eintrag, der weder.
noch..
ohne Lesen, Speichern oder Sortieren alles ist.Bei POSIX-Shells (die
zsh
sich in dersh
Emulation nur (mehr) POSIX verhalten ) ist es sehr umständlich zu überprüfen, ob ein Verzeichnis nur mit Globs nicht leer ist.Ein Weg ist mit:
(unter der Annahme, dass keine globbezogene Option gegenüber der Standardeinstellung geändert wird (POSIX gibt nur an
noglob
) und dass die VariablenGLOBIGNORE
(fürbash
) undFIGNORE
(fürksh
) nicht festgelegt sind und dass (füryash
) keiner der Dateinamen Folgen von Bytes enthält, die keine gültigen Zeichen bilden ).Die Idee ist, dass in POSIX-Shells ein Globus, wenn er nicht übereinstimmt, nicht erweitert wird (eine Fehlfunktion, die die Bourne-Shell Ende der 70er Jahre eingeführt hat). Also mit
set -- *
, wenn wir bekommen$1
==*
, wir wissen nicht , ob es war , weil es keine Übereinstimmung war oder ob es eine Datei mit dem Namen*
.Ihr (fehlerhafter) Ansatz, um das zu umgehen, war zu verwenden
[ -e "$1" ]
. Hier verwenden wir stattdessenset -- [*] *
. Dies ermöglicht es, die beiden Fälle zu unterscheiden, denn wenn keine Datei vorhanden ist, bleibt die oben genannte bestehen[*] *
, und wenn eine Datei aufgerufen*
wird, wird dies* *
. Ähnliches tun wir für versteckte Dateien. Das ist etwas umständlich, da die Bourne-Shell (ebenfalls behoben durchzsh
die Forsyth-Shell, pdksh undfish
) eine weitere Fehlfunktion aufweist, bei der die Erweiterung von.*
die speziellen (Pseudo-) Einträge enthält.
und von..
gemeldet wirdreaddir()
.Damit es in all diesen Muscheln funktioniert, können Sie Folgendes tun:
In jedem Fall ist die Syntax
zsh
von standardmäßig nicht mit der POSIX sh-Syntax kompatibel, da sie die meisten Hauptprobleme in der Bourne-Shell (lange bevor POSIX.2 erstmals veröffentlicht wurde) auf nicht abwärtskompatible Weise behoben hat, einschließlich dieser*
links unexpanded wenn es keine Übereinstimmung ist (pre-Bourne - Shells nicht das Problem hatte,csh
,tcsh
undfish
nicht entweder) und.*
darunter.
und..
aber einige andere wie Split + glob auf Parameter oder arithmetische Erweiterung durchgeführt, so dass Sie nicht erwarten können Code geschrieben in der POSIX sh, um immer zu arbeiten, eszsh
sei denn, Sie aktivieren diesh
Emulation.Diese
sh
Emulation ist besonders vorhanden, damit Sie POSIX-Code in verwenden könnenzsh
.Wenn Sie möchten ,
source
eine Datei in POSIX geschriebensh
innenzsh
, können Sie tun:Dann wird diese Datei in der
sh
Emulation ausgewertet und jede darin deklarierte Funktion behält diesen Emulationsmodus bei.quelle
Während das Standardverhalten von zsh darin besteht, einen Fehler auszugeben, wird dies durch die
nomatch
Option gesteuert . Sie können die Option deaktivieren, um die Option wie*
Bash und Dash an Ort und Stelle zu belassen:Während dieser Befehl in keinem der anderen Befehle funktioniert, können Sie dies einfach ignorieren:
Dies wird
setopt
auf zsh ausgeführt und unterdrückt die Fehlerausgabe (2>/dev/null
) und den Rückkehrcode (|| true
) des fehlgeschlagenen Befehls auf den anderen.Wie geschrieben ist es problematisch, wenn beispielsweise eine Datei vorhanden ist
-e
: Dann werden Sieset -e
die Shell-Optionen ausführen und ändern, um sie zu beenden, wenn ein Befehl fehlschlägt. Es gibt schlechtere Ergebnisse, wenn Sie kreativ sind.set -- *
wird sicherer sein und die Optionsänderungen verhindern.quelle
Die Syntax von Zsh ist nicht mit sh kompatibel. Es ist nah genug, um wie sh auszusehen, aber nicht nah genug, um sh-Code unverändert auszuführen.
Wenn Sie sh-Code in zsh ausführen möchten, z. B. weil Sie eine sh-Funktion oder ein Snippet für sh geschrieben haben, das Sie in einem zsh-Skript verwenden
emulate
möchten , können Sie das integrierte Programm verwenden . So beschaffen Sie beispielsweise eine für sh in einem zsh-Skript geschriebene Datei:Um eine Funktion oder ein Skript in sh-Syntax zu schreiben und die Ausführung in zsh zu ermöglichen, setzen Sie dies am Anfang:
In der sh-Syntax unterstützt zsh alle POSIX-Konstrukte (es ist ungefähr so POSIX-kompatibel wie
bash --posix
oder ksh93 oder mksh). Es unterstützt auch einige ksh- und bash-Erweiterungen wie Arrays (0-indiziert in ksh, in bash und darunteremulate sh
, aber 1-indiziert in nativem zsh) und[[ … ]]
. Wenn Sie POSIX sh plus ksh globs möchten, verwenden Sieemulate … ksh …
anstelle vonemulate … sh …
und fügen Sieif [[ -n $BASH ]]; then shopt -s extglob; fi
für Bash hinzu (beachten Sie, dass dies nicht lokal für das Skript / die Funktion ist).Die native zsh-Methode zum Auflisten aller Einträge in einem Verzeichnis mit Ausnahme von
.
und..
istDabei werden die Glob-Qualifizierer verwendet
D
, um Punktdateien einzuschließen undN
eine leere Liste zu erstellen, wenn keine Übereinstimmungen vorhanden sind.Die native zsh-Methode zum Auflisten aller Einträge in einem Verzeichnis außer
.
und..
ist viel komplizierter. Sie müssen Punktdateien auflisten. Wenn Sie Dateien im aktuellen Verzeichnis oder in einem Pfad auflisten, der nicht als absolut garantiert ist, müssen Sie vorsichtig sein, falls ein Dateiname mit einem Bindestrich beginnt. Hier ist eine Möglichkeit, dies zu tun, indem Sie die Muster verwenden,..?* .[!.]* *
um alle Dateien außer.
und..
aufzulisten und nicht erweiterte Muster zu entfernen.Wenn Sie nur testen möchten, ob ein Verzeichnis leer ist, gibt es einen viel einfacheren Weg.
quelle
if ls -A | read q; then echo not empty; else echo empty; fi
Der
set
tragbarste Weg wäre via und globstar für alle POSIX-kompatiblen Shells. Dies wurde in Gilles 'Antwort auf eine verwandte Frage gezeigt. Ich habe die Methode leicht in eine Funktion umgewandelt:Das große Problem mit
zsh
ist , dass währendksh
undbash
verhalten sich in mehr oder weniger konsequent - das ist , wenn wir tun ,set * .*
werden Sie drei Positionsparameter haben* . ..
in wirklich leeres Verzeichnis - inzsh
Sie erhalten* .*
als Positionsparameter. Glücklicherweisefor i ; do ... done
funktioniert zumindest das Durchlaufen von Positionsparametern konsistent. Der Rest ist nur eine Iteration und eine Überprüfung auf das Vorhandensein des Dateinamens mit.
und..
übersprungen.Probieren Sie es online in ksh!
Probieren Sie es online in zsh!
quelle
zsh
. Leider haben wir unszsh
entschieden, den seltsamen Weg anstelle eines ähnlichen Verhaltens wie beibash
anderen Shells zu gehen, da laut POSIX: "Wenn das Muster keinem Pfadnamen entspricht, wird die zurückgegebene Anzahl übereinstimmender Pfade auf 0 gesetzt und der Inhalt von pglob- > gl_pathv sind implementierungsdefiniert. " Quellenomatch
überall umdrehen, besteht die Gefahr, dass Code beschädigt wird, der die Standardeinstellung (und die sehr vernünftige) voraussetzt,sh
undbash
dass dies falsch ist.nomatch
, ist das das Beste, was wir bekommen haben. Wir können es natürlich auch zurückschalten, bevor die Funktion beendet wird. Oder wir könnten einfach Shell-Wege aufgeben und einfach etwas anderes verwenden, wiefind
zum Beispiel.zsh
Funktionen anstelle von Konsistenz bevorzugt werden. Hoffe das hilft etwas..*
nicht enthalten.
und..
ist das, was Sie im Allgemeinen wollen und wurde in der gemeinfreien Version vonksh
zuvor durchgeführtzsh
(pdksh hat es von der Forsyth-Shell geerbt, auf der es basiert). In anderen Schalen , ob.
und..
enthalten ist , hängt davon ab , obreaddir()
kehrt sie oder nicht , die nicht garantiert ist.Ich bin mir bei der
set *
Lösung nicht sicher . Wie wäre es damit?Getestet und das funktioniert gut für
zsh
,bash
,ash
,ksh
,bourne
AKAheirloom
quelle