Excluir de * en línea de comando

14

Hay muchas situaciones en las que el uso de a *es prácticamente inevitable, por ejemplo, rm -rf *en una carpeta que contiene miles de subcarpetas y archivos.

Pero, ¿qué sucede si desea excluir solo uno o dos archivos o carpetas del rmcomando? Busqué en Google y solo encontré soluciones bastante complicadas, find . -depth -not \( -name 'one' -o -name 'two' \ -o -name 'three' \) -exec rm {} \;como se indica aquí .

¿Existe la posibilidad de hacer esto de una manera más fácil, sin ese desvío find? Por ejemplo, rm -rf --exclude='one' --exclude='two' --exclude='three' *como en rsync o simplemente rm -rf -e 'one','two','three' *?

Tal vez incluso una posibilidad general de excluir cosas de *(por lo demás comandos como cp, mv, ... no tienen que poner en práctica su propia)? ¿Algo así *{'one','two','three'}o algo así?

David
fuente
3
Si desea mantener solo uno o dos archivos, entonces la forma más simple y fácil sería mover esos archivos a otra ubicación, borrar todos los archivos restantes y mover los que retuvo. Si desea mantener muchos más archivos que usaría findcon la --deleteopción (no es necesario ejecutar rmpara cada archivo. Esa es una sobrecarga innecesaria).
Hennes
Esa sería una posibilidad, pero es casi tan elaborada como la que mencioné. Sé que podría combinar los comandos para algo así mv -t /tmp one two three && rm -rf * && mv -t . /tmp/one /tmp/two /tmp/three, pero preferiría una solución que ofrezca la posibilidad de excluir explícitamente algo de *. Seguramente habrá situaciones en las que mover o copiar los archivos a otro destino no será una opción.
David
2
Es, por eso no lo publiqué como respuesta. Simplemente como una forma menos compleja de hacer las cosas (tres comandos muy simples versus uno algo más complejo). Odio la complejidad en combinación con rm.
Hennes

Respuestas:

33

Para Bash hay una opción de shell llamada extglobque está desactivada por defecto para mantener la compatibilidad con la sintaxis de shell estándar. Extglob complementa la sintaxis de los operadores adicionales como !(), ?(), @()y algunos más.

Para encender extglob, escriba shopt -s extglob. Para mantenerlo encendido para el tipo de usuario actual echo 'shopt -s extglob' >> ~/.bashrc.

Para el ejemplo rm: Con extglobusted puede usar

rm -rf !(one|two|three)
choroba
fuente
Parece una buena solución, pero tampoco quiero encenderla y apagarla cada vez que me encuentro en una situación como la descrita.
David
3
@David: podrías poner la shoptcosa en tu interior ~/.bashrc, no puede doler.
enzotib
Si lo hago bien, la opción extglob no cambia el efecto *, sino que agrega indicadores similares (como el !) que tienen funciones adicionales. En ese caso, sería una solución encantadora.
David
1
@David: Exactamente.
choroba
1
@David Para mantener la compatibilidad con la sintaxis de shell estándar . Es probable que haya otras situaciones en las que el comportamiento puede cambiar.
gerrit
4

He construido excepto exactamente eso (sry que todavía no pude agregar documentación).

Tengo una carpeta como la siguiente:

$ ls
a b c d

Escribir except b dte dará ayc

$ except b d
ac

Ahora puedes canalizar eso a rm.

except b d | xargs -0 rm

Esto puede ser difícil de recordar, entonces, ¿por qué no solo construir un script encima de excepto, llamado rm-except:

#!/usr/bin/env bash

except "$@" | xargs -0 rm

Del mismo modo fácil es ls-except:

#!/usr/bin/env bash

except "$@" | xargs -0 ls
Método de ayuda
fuente
3
Esto es bueno, pero no es realmente necesario cuando conoces extglob - askubuntu.com/a/329233/138369
David
1

Como alternativa a extglob (aunque esa es una muy buena respuesta y todos deberían tenerla shopt -s extglob globstaren su .bashrc), puede usar la variable global $ GLOBIGNORE . Digamos que desea obtener todos los archivos excepto 'foo.txt' y 'bar baz.txt':

GLOBIGNORE=foo.txt:'bar baz.txt'

... sin embargo, esto activará la opción de shell dotglob, lo que significa que *coincidirá con los archivos que comienzan con un punto (que normalmente están ocultos). Entonces realmente necesitas dos comandos:

GLOBIGNORE=foo.txt:'bar baz.txt'
shopt -u dotglob

Dado que esta es una variable global, afectará cada globo que use hasta que $ GLOBSTAR se desarme al cerrar sesión o con

GLOBIGNORE=

También funcionará solo en las cadenas literales pasadas a la variable. Puede ver a qué me refiero estableciendo $ GLOBIGNORE y observando la diferencia entre estos comandos:

printf '%s\n' *
printf '%s\n' ./*
maldad
fuente