Ma réponse tldr est:
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/..?*" ]
}
Il est compatible POSIX et, peu importe, il est généralement plus rapide que la solution qui répertorie le répertoire et dirige la sortie vers grep.
Usage:
if emptydir adir
then
echo "nothing found"
else
echo "not empty"
fi
J'aime la réponse /unix//a/202276/160204 , que je réécris comme:
function emptydir {
! { ls -1qA "./$1/" | grep -q . ; }
}
Il répertorie le répertoire et dirige le résultat vers grep. Au lieu de cela, je propose une fonction simple qui est basée sur l'expansion et la comparaison globales.
function emptydir {
[ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}
Cette fonction n'est pas POSIX standard et appelle un sous-shell avec $()
. J'explique cette fonction simple d'abord afin que nous puissions mieux comprendre la solution finale (voir la réponse tldr ci-dessus) plus tard.
Explication:
Le côté gauche (LHS) est vide lorsqu'aucune expansion ne se produit, ce qui est le cas lorsque le répertoire est vide. L'option nullglob est requise car sinon, en l'absence de correspondance, le glob lui-même est le résultat de l'expansion. (Le fait que le RHS corresponde aux globes du LHS lorsque le répertoire est vide ne fonctionne pas en raison des faux positifs qui se produisent lorsqu'un glob LHS correspond à un seul fichier nommé glob: *
le glob correspond à la sous-chaîne *
du nom de fichier. ) L'expression d'accolade {,.[^.],..?}
couvre les fichiers cachés, mais pas ..
ou .
.
Parce qu'il shopt -s nullglob
est exécuté à l'intérieur $()
(un sous-shell), il ne change pas l' nullglob
option du shell actuel, ce qui est normalement une bonne chose. D'un autre côté, c'est une bonne idée de définir cette option dans les scripts, car il est sujet à erreur d'avoir un glob qui renvoie quelque chose lorsqu'il n'y a pas de correspondance. Ainsi, on pourrait définir l'option nullglob au début du script et elle ne sera pas nécessaire dans la fonction. Gardons cela à l'esprit: nous voulons une solution qui fonctionne avec l'option nullglob.
Mises en garde:
Si nous n'avons pas accès en lecture au répertoire, la fonction signale la même chose que s'il y avait un répertoire vide. Cela s'applique également à une fonction qui répertorie le répertoire et grep la sortie.
La shopt -s nullglob
commande n'est pas POSIX standard.
Il utilise le sous-shell créé par $()
. Ce n'est pas un gros problème, mais c'est bien si nous pouvons l'éviter.
Pro:
Ce n'est pas vraiment important, mais cette fonction est quatre fois plus rapide que la précédente, mesurée avec la quantité de temps CPU passé dans le noyau au cours du processus.
Autres solutions:
Nous pouvons supprimer la shopt -s nullglob
commande non POSIX sur le LHS et mettre la chaîne "$1/* $1/.[^.]* $1/..?*"
dans le RHS et éliminer séparément les faux positifs qui se produisent lorsque nous n'avons que des fichiers nommés '*'
, .[^.]*
ou ..?*
dans le répertoire:
function emptydir {
[ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
[ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}
Sans la shopt -s nullglob
commande, il est désormais logique de supprimer le sous-shell, mais nous devons être prudents car nous voulons éviter le fractionnement de mots tout en permettant l'expansion globale sur le LHS. En particulier, la citation pour éviter le fractionnement de mots ne fonctionne pas, car elle empêche également l'expansion des globes. Notre solution consiste à considérer les globes séparément:
function emptydir {
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Nous avons encore le fractionnement de mots pour le glob individuel, mais ça va maintenant, car cela ne produira une erreur que lorsque le répertoire n'est pas vide. Nous avons ajouté 2> / dev / null, pour supprimer le message d'erreur lorsqu'il y a beaucoup de fichiers correspondant au glob donné sur le LHS.
Nous rappelons que nous voulons une solution qui fonctionne également avec l'option nullglob. La solution ci-dessus échoue avec l'option nullglob, car lorsque le répertoire est vide, le LHS est également vide. Heureusement, il ne dit jamais que le répertoire est vide alors qu'il ne l'est pas. Il ne fait que dire qu'il est vide quand il l'est. Nous pouvons donc gérer séparément l'option nullglob. Nous ne pouvons pas simplement ajouter les cas [ "$1/"* = "" ]
etc., car ceux-ci se développeront en tant que [ = "" ]
, etc. qui sont syntaxiquement incorrects. Nous utilisons donc [ "$1/"* "" = "" ]
etc. à la place. Nous devons à nouveau considérer les trois cas *
, ..?*
et .[^.]*
faire correspondre les fichiers cachés, mais pas .
et..
. Ceux-ci n'interféreront pas si nous n'avons pas l'option nullglob, car ils ne disent jamais non plus qu'il est vide alors qu'il ne l'est pas. Ainsi, la solution finale proposée est:
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/..?*" ]
}
Problème de sécurité:
Créez deux fichiers rm
et x
dans un répertoire vide et exécutez *
à l'invite. Le glob *
se développera rm x
et sera exécuté pour être supprimé x
. Ce n'est pas un problème de sécurité, car dans notre fonction, les globes sont situés là où les extensions ne sont pas vues comme des commandes, mais comme des arguments, tout comme dans for f in *
.