cómo verificar que el directorio esté vacío

15

Tengo un requisito, si ejecuto un script ./123con argumentos de ruta vacía, digamos /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(este directorio está vacío). Debería mostrar "el directorio está vacío"

Mi código es:

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

Tengo un error que aparece como, si ejecuto el nombre del script ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(este directorio está vacío).

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

en lugar de mostrar la salida solo "el directorio está vacío" muestra la salida anterior

La salida a continuación debe mostrarse si excedo el script con los argumentos correctos (quiero decir con la ruta de directorio correcta). decir./123 /usr/share

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

mi salida esperada es: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.
Buda sreekanth
fuente
44
Ver mywiki.wooledge.org/BashFAQ/004
Valentin Bajrami
¿Alguien puede verificar mi código una vez? Lo
edité
Relacionado: superuser.com/questions/352289/...
AlikElzin-Kilaka

Respuestas:

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

Lo anterior es una prueba compatible con POSIX, y debería ser muy rápido. lsmostrará una lista de todos los archivos / directorios en un directorio con excepción .y ..cada uno por línea y se -quote todos los caracteres no imprimibles (para incluir \newlines) en la salida con un ?signo de interrogación. De esta manera, si greprecibe incluso un solo carácter en la entrada, devolverá verdadero, de lo contrario, falso.

Para hacerlo solo en un 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"

Un shell POSIX (que no ha deshabilitado -fanteriormente la generación de ilename) colocará setla "$@"matriz de parámetros posicionales en las cadenas literales seguidas por el setcomando anterior o en los campos generados por los operadores glob al final de cada uno. Si lo hace, depende de si los globos realmente coinciden con algo. En algunos proyectiles, puede indicarle a un globo que no se resuelve que se expanda a nulo, o nada en absoluto. Esto a veces puede ser beneficioso, pero no es portátil y a menudo viene con problemas adicionales, como tener que configurar opciones especiales de shell y luego desarmarlas.

El único medio portátil de manejar argumentos de valor nulo involucra variables vacías o no definidas o ~expansiones de tilde. Y el último, por cierto, es mucho más seguro que el primero.

Por encima del shell solo se prueba la -eexistencia de cualquiera de los archivos para xistence si ninguno de los tres globos especificados resuelve más de una coincidencia. Por lo tanto, el forciclo solo se ejecuta en absoluto para tres o menos iteraciones, y solo en el caso de un directorio vacío, o en el caso de que uno o más de los patrones se resuelvan solo en un solo archivo. El fortambién breaks si alguno de los globos representan un archivo real - y, como he dispuesto los globos en el orden de mayor probabilidad de que menos, debe dejar de fumar más o menos en la primera iteración cada vez.

De cualquier manera, debe implicar solo una stat()llamada al sistema : el shell y lsambos solo deben stat()consultar el directorio consultado y enumerar los archivos que sus dentries informan que contiene. Esto se contrasta con el comportamiento del findcual, en su lugar, se incluiría stat()cada archivo que pueda incluir en la lista.

mikeserv
fuente
Puede confirmar. Uso el ejemplo inicial de mikeserv en los scripts que he escrito.
Oteo
Eso informaría como directorios vacíos que no existen o para los que no tiene acceso de lectura. Para el segundo, si tiene acceso de lectura pero no de acceso de búsqueda, YMMV.
Stéphane Chazelas
@ StéphaneChazelas, tal vez, pero dichos informes irán acompañados de mensajes de error para notificar al usuario.
mikeserv
1
Solo en el lscaso. Glob y [están en silencio cuando no tienen acceso. Por ejemplo, [ -e /var/spool/cron/crontabs/stephane ]informará silenciosamente falso, mientras haya un archivo con ese nombre.
Stéphane Chazelas
También tenga en cuenta que esas soluciones no son equivalentes si somedir es un enlace simbólico a un directorio (o no un directorio).
Stéphane Chazelas
6

Con GNU o BSD modernos find, puede hacer:

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

(asume $dirno se ve como un findpredicado ( !, (, -name...).

POSIXY:

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
fuente
4

[-z $dir ]se queja de que no hay un comando llamado [-zen la mayoría de los sistemas. Necesita espacios alrededor de los corchetes .

[ -z $dir ]resulta ser verdadero si direstá vacío, y es falso para la mayoría de los demás valores de dir, pero no es confiable, por ejemplo, es verdadero si el valor de dires = -zo -o -o -n -n. Utilice siempre comillas dobles alrededor de las sustituciones de comandos (esto también se aplica al resto de su secuencia de comandos).

[ -z "$dir" ]prueba si el valor de la variable direstá vacío. El valor de la variable es una cadena, que resulta ser la ruta al directorio. Eso no te dice nada sobre el directorio en sí.

No hay operador para probar si un directorio está vacío, como lo hay para un archivo normal ( [ -s "$dir" ]es cierto para un directorio incluso si está vacío). Una forma sencilla de probar si un directorio está vacío es enumerar su contenido; Si obtiene texto vacío, el directorio está vacío.

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

En sistemas más antiguos que no tienen ls -A, puede usar ls -a, pero luego .y ..están listados.

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

(No sangra la línea que comienza ..ya que la cadena debe contener solo una nueva línea, no una nueva línea y espacio adicional).

Gilles 'SO- deja de ser malvado'
fuente
¿podría explicar esta parte "$ (ls -A -" $ dir ")"? ¿Qué hace $ dentro y fuera de los corchetes?
Buda sreekanth
@buddhasreekanth $(…)es la sustitución del comando
Gilles 'SO- deja de ser malvado'
@Giles "$ (ls -A -" $ dir ")" no está funcionando su error de lanzamiento.
Buda sreekanth
@buddhasreekanth ¿Cuál es el error? Copiar pegar. No puede esperar que la gente lo ayude si simplemente dice "no funciona" sin explicar exactamente lo que observa.
Gilles 'SO- deja de ser malo'
@Giles este es el error. Cuando ejecuté mi script ./filestats su error de lanzamiento .. $ / filestats / home / sreekanth / testdir / aki / schatz tamaño mínimo de archivo: 4096 / home / sreekanth / testdir / aki / schatz tamaño máximo de archivo: 4096 / home / sreekanth / testdir / aki / schatz tamaño promedio de archivo: 4 directorio está vacío. En lugar de mostrar "el directorio está vacío". Su visualización con tamaño mínimo, tamaño máximo y tamaño promedio.
Buda sreekanth
3

Estás viendo los atributos del directorio en sí.

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

Desea contar cuántos archivos hay allí:

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

Entonces, la prueba para "directorio está vacío" es:

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

Me gusta esto:

$ 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
fuente
Eso informará que el directorio está vacío si no tiene acceso de lectura o si no existe.
Stéphane Chazelas
1

Mi respuesta tldr es:

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

Cumple con POSIX y, no es que importe mucho, generalmente es más rápido que la solución que enumera el directorio y canaliza la salida a grep.

Uso:

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

Me gusta la respuesta /unix//a/202276/160204 , que reescribo como:

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

Enumera el directorio y canaliza el resultado a grep. En cambio, propongo una función simple que se basa en la expansión y comparación global.

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

Esta función no es POSIX estándar y llama a una subshell con $(). Primero explico esta función simple para que podamos comprender mejor la solución final (ver la respuesta tldr más arriba) más adelante.

Explicación:

El lado izquierdo (LHS) está vacío cuando no se produce expansión, que es el caso cuando el directorio está vacío. Se requiere la opción nullglob porque, de lo contrario, cuando no hay coincidencia, el glob en sí es el resultado de la expansión. (Tener el RHS coincide con los globos del LHS cuando el directorio está vacío no funciona debido a los falsos positivos que ocurren cuando un globo del LHS coincide con un solo archivo llamado como el globo en sí: el *en el globo coincide con la subcadena *en el nombre del archivo. ) La expresión de llaves {,.[^.],..?}cubre archivos ocultos, pero no ..o ..

Debido a que shopt -s nullglobse ejecuta dentro $()(un subshell), no cambia la nullglobopción del shell actual, que normalmente es algo bueno. Por otro lado, es una buena idea establecer esta opción en los scripts, ya que es propenso a errores que un glob devuelva algo cuando no hay coincidencia. Entonces, uno podría establecer la opción nullglob al comienzo del script y no será necesaria en la función. Tengamos esto en cuenta: queremos una solución que funcione con la opción nullglob.

Advertencias:

Si no tenemos acceso de lectura al directorio, la función informa lo mismo que si hubiera un directorio vacío. Esto se aplica también a una función que enumera el directorio y grep la salida.

El shopt -s nullglobcomando no es POSIX estándar.

Utiliza la subshell creada por $(). No es gran cosa, pero es bueno si podemos evitarlo.

Pro:

No es que realmente importe, pero esta función es cuatro veces más rápida que la anterior, medida con la cantidad de tiempo de CPU en el núcleo dentro del proceso.

Otras soluciones:

Podemos eliminar el shopt -s nullglobcomando que no es POSIX en el LHS y poner la cadena "$1/* $1/.[^.]* $1/..?*"en el RHS y eliminar por separado los falsos positivos que ocurren cuando solo tenemos archivos nombrados '*', .[^.]*o ..?*en el directorio:

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

Sin el shopt -s nullglobcomando, ahora tiene sentido eliminar la subshell, pero debemos tener cuidado porque queremos evitar la división de palabras y, sin embargo, permitir la expansión global en el LHS. En particular, las citas para evitar la división de palabras no funcionan, porque también evitan la expansión global. Nuestra solución es considerar los globos por separado:

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

Todavía tenemos división de palabras para el globo individual, pero ahora está bien, porque solo dará como resultado un error cuando el directorio no esté vacío. Agregamos 2> / dev / null, para descartar el mensaje de error cuando hay muchos archivos que coinciden con el glob dado en el LHS.

Recordamos que queremos una solución que también funcione con la opción nullglob. La solución anterior falla con la opción nullglob, porque cuando el directorio está vacío, los LHS también están vacíos. Afortunadamente, nunca dice que el directorio está vacío cuando no lo está. Solo deja de decir que está vacío cuando lo está. Entonces, podemos administrar la opción nullglob por separado. No podemos simplemente agregar los casos, [ "$1/"* = "" ]etc., porque estos se expandirán como [ = "" ], etc., que son sintácticamente incorrectos. Entonces, usamos [ "$1/"* "" = "" ]etc. en su lugar. De nuevo tenemos que considerar los tres casos *, ..?*y .[^.]*para que coincida con los archivos ocultos, pero no .y... Estos no interferirán si no tenemos la opción nullglob, porque tampoco dicen que está vacía cuando no lo está. Entonces, la solución final propuesta es:

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

Preocupación de seguridad:

Cree dos archivos rmy xen un directorio vacío y ejecútelos *en la solicitud. El globo *se expandirá rm xy se ejecutará para eliminar x. Esto no es un problema de seguridad, porque en nuestra función, los globos se encuentran donde las expansiones no se ven como comandos, sino como argumentos, al igual que en for f in *.

Dominic108
fuente
$(set -f; echo "$1"/*)Parece una forma bastante complicada de escribir "$1/*". Y esto no coincidirá con directorios o archivos ocultos.
muru
Lo que quise decir es que no hay expansión de nombre de archivo en "$1/*"(tenga en cuenta que las comillas incluyen *), por lo que el set -fy la subshell son innecesarios para eso en particular.
muru
Responde a una pregunta diferente: si el directorio contiene archivos o directorios no ocultos. Lo siento por eso. Estaré encantado de eliminarlo.
Dominic108
1
Hay maneras para que coincida con los archivos ocultos también (ver el pegote complicado en la respuesta de mikeserv : ./* ./.[!.]* ./..?*). Tal vez pueda incorporar eso para completar la función.
muru
Ah! ¡Miraré esto y aprenderé y tal vez tendremos una respuesta completa basada en la expansión global!
Dominic108
0

Aquí hay otra forma simple de hacerlo. Suponga que D es el nombre completo de la ruta del directorio que desea probar para el vacío.

Luego

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

Esto funciona porque

du -s D

tiene como salida

size_of_D   D

awk elimina la D y si el tamaño es 0, el directorio está vacío.

Richard Gostanian
fuente
-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
usuario137815
fuente
2
Hola y bienvenidos al sitio. No publique respuestas que sean solo código y ninguna explicación. Comente el código o explique lo que está haciendo. Por cierto, no hay razón para usar echo, todo lo que necesitas es list="$somedir/*". Además, esto es complicado y propenso a errores. No ha mencionado que el OP debería dar la ruta del directorio de destino, incluida la barra diagonal, por ejemplo.
terdon