Patrón de nombre de archivo de shell que se expande a archivos de puntos pero no a `..`?

13

Recientemente tuve un pequeño accidente causado por un patrón de caparazón que se expandió de manera inesperada. Quería cambiar el propietario de un montón de archivos de puntos en el /rootdirectorio, así que lo hice

chown -R root .*

Naturalmente, la .*expansión a la ..que fue un poco un desastre.

Sé que basheste comportamiento se puede cambiar ajustando alguna opción de shell, pero con la configuración predeterminada, ¿hay algún patrón que se expanda a cada archivo de puntos en el directorio pero no a .y ..?

Ernest A
fuente

Respuestas:

20

Bash, ksh y zsh tienen mejores soluciones, pero en esta respuesta supongo un shell POSIX.

El patrón .[!.]*coincide con todos los archivos que comienzan con un punto seguido de un carácter sin punto. (Tenga en cuenta que [^.]es compatible con algunos shells pero no todos, la sintaxis portátil para el complemento de juego de caracteres en patrones comodín es [!.]). Por lo tanto, excluye .y .., pero también archivos que comienzan con dos puntos. El patrón ..?*maneja archivos que comienzan con dos puntos y no son justos ...

chown -R root .[!.]* ..?*

Este es el patrón clásico configurado para coincidir con todos los archivos:

* .[!.]* ..?*

Una limitación de este enfoque es que si uno de los patrones no coincide con nada, se pasa al comando. En un script, cuando desea hacer coincidir todos los archivos en un directorio excepto .y .., hay varias soluciones, todas ellas engorrosas:

  • Use * .*para enumerar todas las entradas y excluir .y ..en un bucle. Uno o ambos patrones pueden no coincidir con nada, por lo que el ciclo debe verificar la existencia de cada archivo. Tiene la oportunidad de filtrar según otros criterios; por ejemplo, elimine la -hprueba si desea omitir los enlaces simbólicos colgantes.

    for x in * .*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
  • Una variante más compleja donde el comando se ejecuta solo una vez. Tenga en cuenta que los parámetros posicionales están cubiertos (los shells POSIX no tienen arrays); pon esto en una función separada si esto es un problema.

    set --
    for x in * .[!.]* ..?*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      set -- "$@" "$x"
    done
    somecommand "$@"
  • Usa el * .[!.]* ..?*tríptico. Una vez más, uno o más de los patrones pueden no coincidir con nada, por lo que debemos verificar los archivos existentes (incluidos los enlaces simbólicos colgantes).

    for x in * .[!.]* ..?*; do
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
  • Use el * .[!.]* ..?*tryptich y ejecute el comando una vez por patrón, pero solo si coincide con algo. Esto ejecuta el comando solo una vez. Tenga en cuenta que los parámetros posicionales están cubiertos (los shells POSIX no tienen matrices), ponga esto en una función separada si esto es un problema.

    set -- *
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- .[!.]* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- ..?* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    somecommand "$@"
  • Uso find. Con GNU o BSD find, evitar la recursividad es fácil con las opciones -mindepthy -maxdepth. Con POSIX find, es un poco más complicado, pero se puede hacer. Este formulario tiene la ventaja de permitir fácilmente ejecutar el comando una sola vez en lugar de una vez por archivo (pero esto no está garantizado: si el comando resultante es demasiado largo, el comando se ejecutará en varios lotes).

    find . -name . -o -exec somecommand {} + -o -type d -prune
Gilles 'SO- deja de ser malvado'
fuente
6

Esto funciona si todos los nombres de archivo contienen al menos tres caracteres (incluido el punto):

chown -R root .??*

Para una solución más robusta, puede usar find:

find . -maxdepth 1 -name '.*' -exec chown -R root {} \;

fuente