comment vérifier que le répertoire est vide

15

J'ai une exigence, si j'exécute un script ./123avec des arguments de chemin vide, disons /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(ce répertoire est vide). Il doit afficher "le répertoire est vide"

Mon code est:

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

J'ai une erreur qui s'affiche comme si j'exécute le nom du script ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(ce répertoire est vide).

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

au lieu d'afficher uniquement la sortie "le répertoire est vide", il affiche la sortie ci-dessus

La sortie ci-dessous doit être affichée si j'exécute le script avec des arguments corrects (je veux dire avec un chemin de répertoire correct). dire./123 /usr/share

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

ma sortie attendue est: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.
Bouddha Sreekanth
la source
4
Voir mywiki.wooledge.org/BashFAQ/004
Valentin Bajrami
quelqu'un peut-il vérifier mon code une fois. Je l'ai édité
buddha sreekanth
En relation: superuser.com/questions/352289/…
AlikElzin-kilaka

Réponses:

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

Ce qui précède est un test compatible POSIX - et devrait être très rapide. lslistera tous les fichiers / répertoires dans un répertoire à l' exception .et ..chacun par ligne et sera -qUote tous les caractères non-imprimables (pour inclure \newlines) dans la sortie avec un ?point d'interrogation. De cette façon, si grepreçoit même un seul caractère en entrée, il retournera vrai - sinon faux.

Pour le faire dans un shell POSIX seul:

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"

Un POSIX-coquille (qui n'a pas désactivé auparavant -fgénération de ilename) sera setla "$@"matrice-paramètre de positionnement soit aux chaînes littérales suivies par la setcommande ci - dessus, ou bien pour les champs générés par les opérateurs de glob à la fin de chaque. Que ce soit le cas, cela dépend si les globes correspondent réellement à quelque chose. Dans certains shells, vous pouvez demander à un glob non résolu de s'étendre à null - ou rien du tout. Cela peut parfois être bénéfique, mais il n'est pas portable et s'accompagne souvent de problèmes supplémentaires - tels que la nécessité de définir des options de shell spéciales et de les désactiver ensuite.

Le seul moyen portable de gérer des arguments à valeur nulle implique des variables vides ou non définies ou ~des extensions tilde. Et ce dernier, soit dit en passant, est beaucoup plus sûr que le premier.

Au-dessus du shell, les tests de -existence des fichiers ne sont testés que si aucun des trois globs spécifiés ne se résout en plusieurs correspondances. Ainsi, la forboucle n'est exécutée que pour trois itérations ou moins, et uniquement dans le cas d'un répertoire vide, ou dans le cas où un ou plusieurs des modèles ne se résolvent qu'en un seul fichier. Le forégalement breaks si l' une des petites boules représentent un fichier réel - et comme je l' ai arrangé les petites boules dans l'ordre le plus probable au moins probable, il devrait quitter à peu près à la première itération à chaque fois.

Dans tous les cas, cela ne devrait impliquer qu'un seul stat()appel système - le shell et lsne devrait à la fois avoir besoin que stat()du répertoire interrogé et répertorier les fichiers que son rapport de dentisterie contient. Ceci est contrasté par le comportement de findce qui serait à la place de stat()chaque fichier que vous pourriez lister avec.

mikeserv
la source
Peut confirmer. J'utilise l'exemple initial de mikeserv dans les scripts que j'ai écrits.
Otheus
Cela serait signalé comme des répertoires vides qui n'existent pas ou pour lesquels vous n'avez pas accès en lecture. Pour le second, si vous disposez d'un accès en lecture mais pas d'un accès en recherche, YMMV.
Stéphane Chazelas
@ StéphaneChazelas - peut-être, mais de tels rapports seront accompagnés de messages d'erreur pour en informer l'utilisateur.
mikeserv
1
Seulement dans le lscas. globes et [sont silencieux quand ils n'y ont pas accès. Par exemple, [ -e /var/spool/cron/crontabs/stephane ]signalera silencieusement false, alors qu'il existe un fichier portant ce nom.
Stéphane Chazelas
Notez également que ces solutions ne sont pas équivalentes si somedir est un lien symbolique vers un répertoire (ou non un répertoire).
Stéphane Chazelas
6

Avec GNU ou BSD modernes find, vous pouvez faire:

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

( ce qui suppose $dirne ressemble pas à un findprédicat ( !, (, -name...).

POSIX:

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
la source
4

[-z $dir ]se plaint qu'aucune commande n'est appelée [-zsur la plupart des systèmes. Vous avez besoin d'espaces autour des supports .

[ -z $dir ]se trouve être vrai si direst vide, et est faux pour la plupart des autres valeurs de dir, mais il n'est pas fiable, par exemple, il est vrai si la valeur de direst = -zou -o -o -n -n. Utilisez toujours des guillemets doubles autour des substitutions de commandes (cela vaut également pour le reste de votre script).

[ -z "$dir" ]teste si la valeur de la variable direst vide. La valeur de la variable est une chaîne, qui se trouve être le chemin d'accès au répertoire. Cela ne vous dit rien sur le répertoire lui-même.

Il n'y a pas d'opérateur pour tester si un répertoire est vide, comme c'est le cas pour un fichier normal ( [ -s "$dir" ]c'est vrai pour un répertoire même s'il est vide). Un moyen simple de tester si un répertoire est vide consiste à répertorier son contenu; si vous obtenez du texte vide, le répertoire est vide.

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

Sur les anciens systèmes qui n'en ont pas ls -A, vous pouvez utiliser ls -a, mais alors .et ..sont répertoriés.

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

(Ne mettez pas en retrait la ligne commençant par, ..car la chaîne doit contenir uniquement une nouvelle ligne, pas une nouvelle ligne et un espace supplémentaire.)

Gilles 'SO- arrête d'être méchant'
la source
pourriez-vous s'il vous plaît expliquer cette partie "$ (ls -A -" $ dir ")". que fait $ entre et en dehors des parenthèses?
buddha sreekanth
@buddhasreekanth $(…)est une substitution de commande
Gilles 'SO- arrête d'être méchant'
@Giles "$ (ls -A -" $ dir ")" ne fonctionne pas son erreur de lancement.
buddha sreekanth
@buddhasreekanth Quelle est l'erreur? Copier coller. Vous ne pouvez pas vous attendre à ce que les gens vous aident si vous dites simplement «ne fonctionne pas» sans expliquer exactement ce que vous observez.
Gilles 'SO- arrête d'être méchant'
@Giles c'est l'erreur. Lorsque j'ai exécuté mon script ./filestats, son erreur de lancement .. $ / filestats / home / sreekanth / testdir / aki / schatz taille minimale du fichier: 4096 / home / sreekanth / testdir / aki / schatz taille maximale du fichier: 4096 / home / sreekanth / testdir / aki / schatz taille moyenne du fichier: le répertoire 4 est vide. Au lieu d'afficher "le répertoire est vide". Son affichage avec une taille minimale, une taille maximale et une taille moyenne.
buddha sreekanth
3

Vous regardez les attributs du répertoire lui-même.

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

Vous voulez compter le nombre de fichiers qui s'y trouvent:

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

Ainsi, le test pour "le répertoire est vide" est:

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

Comme ça:

$ 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
la source
Cela indiquera que le répertoire est vide si vous n'y avez pas accès en lecture ou s'il n'existe pas.
Stéphane Chazelas
1

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 nullglobest exécuté à l'intérieur $()(un sous-shell), il ne change pas l' nullgloboption 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 nullglobcommande 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 nullglobcommande 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 nullglobcommande, 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 rmet xdans un répertoire vide et exécutez *à l'invite. Le glob *se développera rm xet 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 *.

Dominic108
la source
$(set -f; echo "$1"/*)semble une manière d'écrire assez compliquée "$1/*". Et cela ne correspondra pas aux répertoires ou fichiers cachés.
muru
Ce que je voulais dire, c'est qu'il n'y a pas d'extension de nom de fichier dans "$1/*"(notez les guillemets inclus *), donc le set -fsous-shell et ne sont pas nécessaires pour cela en particulier.
muru
Il répond à une question différente: si le répertoire contient des fichiers ou des répertoires non cachés. Je suis désolé. Je me ferai un plaisir de le supprimer.
Dominic108
1
Il existe des moyens pour correspondre à des fichiers cachés trop (voir le glob compliqué dans la réponse de mikeserv : ./* ./.[!.]* ./..?*). Vous pouvez peut-être l'incorporer pour compléter la fonction.
muru
Ah! Je vais regarder cela et apprendre et peut-être que nous aurons une réponse complète basée sur l'expansion de globbing!
Dominic108
0

Voici une autre façon simple de le faire. Supposons que D est le nom de chemin complet du répertoire que vous souhaitez tester pour la vacuité.

ensuite

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

Cela fonctionne parce que

du -s D

a comme sortie

size_of_D   D

awk supprime le D et si la taille est 0, le répertoire est vide.

Richard Gostanian
la source
-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
la source
2
Bonjour et bienvenue sur le site. Veuillez ne pas publier de réponses qui ne sont que du code et aucune explication. Commentez le code ou expliquez autrement ce que vous faites. Soit dit en passant, il n'y a aucune raison d'utiliser echo, tout ce dont vous avez besoin est list="$somedir/*". En outre, cela est délicat et sujet aux erreurs. Vous n'avez pas mentionné que l'OP devrait donner le chemin du répertoire cible, y compris la barre oblique de fin, par exemple.
terdon