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 nullglob
se ejecuta dentro $()
(un subshell), no cambia la nullglob
opció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 nullglob
comando 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 nullglob
comando 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 nullglob
comando, 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 rm
y x
en un directorio vacío y ejecútelos *
en la solicitud. El globo *
se expandirá rm x
y 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 *
.