Unix: elimina archivos y carpetas sin PATTERN

10

Recientemente me enfrenté a la tarea de eliminar todos los archivos / carpetas en un directorio, excepto aquellos que coinciden con un patrón específico. Así que preparé un comando unix de una sola línea para hacer el trabajo. ¿Debe ser solo una línea? Supongo que no, ¡pero definitivamente es más genial así!

Si bien el problema es bastante simple, me sorprendió un poco lo compleja que resultó ser mi solución. Aquí está el comando que usé; NOTA: esta es una solución pobre porque no maneja nombres de archivo que contengan caracteres de avance de línea (lo que no importó en mi situación).

ls | grep -v PATTERN | xargs -n1 -IREPLACE rm -rf REPLACE

No utilicé el comando "buscar" porque no quiero volver a las carpetas que coinciden con PATTERN. Por ejemplo, considere la siguiente estructura de archivos:

file_foo.txt
first_dir
  |
  +--> contents
  +--> ...
foo_dir
  |
  +--> anotherfile.txt
  +--> morefiles.log
foo_file.txt
somefile.txt

El uso del patrón "foo" solo debe eliminar "first_dir" (y su contenido, por supuesto) y "somefile.txt" ( no "anotherfile.txt" o "morefiles.log").

Pregunta, ¿hay formas mejores (más elegantes y correctas) de lograr esto?


EDITAR:
Hace poco me llamó la atención que "encontrar" puede ser una mejor opción:

find * -maxdepth 0 ! -name PATTERN -print0 | xargs -0n1 rm -rf

Esta solución maneja correctamente las rutas que contienen caracteres de avance de línea.

joxl
fuente
EDITAR: cambió "the_dir" erróneo para corregir "first_dir".
joxl

Respuestas:

10

Los siguientes ejemplos tienen un echoprefijo para que pueda probar los patrones antes de usarlos. Retire el echopara activar el rm -rf. Sustituir rm -ripara solicitar confirmación.

ksh tiene una extensión de coincidencia negativa para su globbing:

# ksh
echo rm -rf !(*foo*)

La misma sintaxis está disponible en bash si configura la extglobopción:

# bash
shopt -s extglob
echo rm -rf !(*foo*)

zsh tiene su propia sintaxis para esto:

# zsh
setopt extended_glob
echo rm -rf ^*foo*

También puede usar la sintaxis de estilo ksh :

# zsh: ksh-style glob syntax
setopt ksh_glob no_bare_glob_qual
echo rm -rf !(*foo*)

# zsh: ksh-style glob syntax, adapted for the default bare_glob_qual option
setopt ksh_glob bare_glob_qual
echo rm -rf (!(*foo*))
Chris Johnsen
fuente
Simple, elegante, maneja caracteres de salto de línea, (casi) una línea (he agregado shopt -s extgloba mi archivo .bashrc, por lo que ahora es un trazador de líneas). ¡Perfecto! Prometo un voto positivo tan pronto como mi reputación lo permita.
joxl
7

Aquí hay otra findsolución. No estoy seguro de que esto tenga una ventaja real sobre la suya, pero no lo necesita xargsy permite la rara posibilidad de que * se expanda a demasiados nombres.

find . -maxdepth 1 ! -name PATTERN -type f -delete

También agregué -type fpara que no intentara eliminar directorios.

Advertencia: -deletees poderoso. Le di 0 permisos a uno de mis archivos de prueba y el comando anterior lo eliminó sin dudarlo.

garyjohn
fuente
66
Prueba primero. Reemplace -deletecon -printpara ver si está buscando los archivos que desea. Si todo está bien, use el historial de comandos (por ejemplo, presione el botón arriba) para volver al comando anterior y asegurarse de que está trabajando con los mismos filtros de búsqueda.
Doug Harris
Leído con atención, nota que yo hago deseo de directorios quitar. Y find -deleteno eliminará directorios no vacíos. Observe también que find -maxdepth 1coincide con "." (directorio actual) que es realmente malo. Aunque me gusta su idea de eliminar xargs, uno podría usar find -execalternativamente. find * -maxdepth 0 ! -name PATTERN -exec rm -rf '{}' \;
joxl
0

En su lugar, podría usar este ejemplo de script de rebol http://askcodegeneration.com/keep-subversion/, que es mucho más comprensible y que puede gestionar la interacción del usuario, como la selección y confirmación de carpetas:

delete-file: func[file][
    if/else none? find file "/.svn/" [
        delete file
    ][
        print ["keeping" file]
    ]
]
dir: request-dir
ans: ask rejoin ["Do you confirm " dir "? (Yes): "]
if (ans = "Yes") [
    foreach-file dir :delete-file
]
Tutorial Rebol
fuente