¿Cómo puedo usar comodines inversos o negativos cuando coincida un patrón en un shell de unix / linux?
-
03-07-2019 - |
Pregunta
Diga que quiero copiar el contenido de un directorio, excluyendo los archivos y carpetas cuyos nombres contengan la palabra 'Música'.
cp [exclude-matches] *Music* /target_directory
¿Qué debe ir en lugar de [excluir coincidencias] para lograr esto?
Solución
En Bash puedes hacerlo habilitando la opción extglob
, así (reemplaza ls
con cp
y agrega el directorio de destino, de curso)
~/foobar> shopt extglob
extglob off
~/foobar> ls
abar afoo bbar bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob # Enables extglob
~/foobar> ls !(b*)
abar afoo
~/foobar> ls !(a*)
bbar bfoo
~/foobar> ls !(*foo)
abar bbar
Más tarde, puedes deshabilitar extglob con
shopt -u extglob
Otros consejos
La opción de shell extglob
le brinda una coincidencia de patrones más poderosa en la línea de comandos.
Lo enciendes con shopt -s extglob
, y lo desactivas con shopt -u extglob
.
En tu ejemplo, inicialmente harías:
$ shopt -s extglob
$ cp !(*Music*) /target_directory
El total de ext finalizado glob son operadores de bing (extracto de man bash
):
Si la opción de shell extglob está habilitada mediante el uso de shopt builtin, varios extendidos Se reconocen los operadores de coincidencia de patrones. En la siguiente descripción, una palmadita. tern-list es una lista de uno o más patrones separados por un | Patrones compuestos puede formarse utilizando uno o más de los siguientes sub-patrones:
- ? (lista de patrones)
Coincide con cero o una aparición de los patrones dados- * (lista de patrones)
Coincide con cero o más apariciones de los patrones dados- + (lista de patrones)
Coincide con una o más apariciones de los patrones dados- @ (patrón-lista)
Coincide con uno de los patrones dados- ! (lista de patrones)
Coincide con cualquier cosa excepto uno de los patrones dados
Entonces, por ejemplo, si quisiera enumerar todos los archivos en el directorio actual que no son archivos .c
o .h
, haría:
$ ls -d !(*@(.c|.h))
Por supuesto, el funcionamiento normal del shell, por lo que el último ejemplo también podría escribirse como:
$ ls -d !(*.[ch])
No en bash (que yo sepa), pero:
cp `ls | grep -v Music` /target_directory
Sé que esto no es exactamente lo que buscabas, pero resolverá tu ejemplo.
Si desea evitar el costo de mem de usar el comando exec, creo que puede hacerlo mejor con xargs. Creo que la siguiente es una alternativa más eficiente a
find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec
find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
En bash, una alternativa a shopt -s extglob
es GLOBIGNORE
variable . No es realmente mejor, pero me resulta más fácil de recordar.
Un ejemplo que puede ser lo que quería el póster original:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
Cuando termines, anula GLOBIGNORE
para poder rm * techno *
en el directorio de origen.
También puedes usar un muy simple para
bucle:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
Mi preferencia personal es usar grep y el comando while. Esto le permite a uno escribir scripts potentes pero legibles, asegurándose de que termine haciendo exactamente lo que quiere. Además, al utilizar un comando de eco, puede realizar una ejecución en seco antes de realizar la operación real. Por ejemplo:
ls | grep -v "Music" | while read filename
do
echo $filename
done
imprimirá los archivos que terminará copiando. Si la lista es correcta, el siguiente paso es simplemente reemplazar el comando echo con el comando copy de la siguiente manera:
ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
Una solución para esto se puede encontrar con encontrar.
$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt
Encontrar tiene bastantes opciones, puede ser bastante específico sobre lo que incluye y excluye.
Edit: Adam en los comentarios señaló que esto es recursivo. las opciones de búsqueda mindepth y maxdepth pueden ser útiles para controlar esto.
Un truco que todavía no he visto aquí que no usa extglob
, find
o grep
es tratar dos archivos las listas como conjuntos y " diff " usando comm
:
comm -23 <(ls) <(ls *Music*)
comm
es preferible a diff
porque no tiene crucero adicional.
Esto devuelve todos los elementos del conjunto 1, ls
, que son no también en el conjunto 2, ls * Music *
. Esto requiere que ambos conjuntos estén ordenados para funcionar correctamente. No hay problema para ls
y la expansión global, pero si está usando algo como find
, asegúrese de invocar sort
.
comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
Potencialmente útil.
El siguiente trabajo enumera todos los archivos * .txt
en el directorio actual, excepto aquellos que comienzan con un número.
Esto funciona en bash
, dash
, zsh
y todos los demás shells compatibles con POSIX.
for FILE in /some/dir/*.txt; do # for each *.txt file
case "${FILE##*/}" in # if file basename...
[0-9]*) continue ;; # starts with digit: skip
esac
## otherwise, do stuff with $FILE here
done
-
En la línea uno, el patrón
/some/dir/*.txt
hará que el buclefor
se repita en todos los archivos en/ some / dir
cuyo nombre termina con.txt
. -
En la línea dos, una declaración de caso se usa para eliminar archivos no deseados. - La expresión
$ {FILE ## * /}
elimina cualquier componente del nombre de directorio principal del nombre de archivo (aquí/ some / dir /
) para que los patrones solo puedan coincidir El nombre base del archivo. (Si solo está eliminando nombres de archivos basados ??en sufijos, puede acortar esto a$ FILE
en su lugar). -
En la línea tres, todos los archivos que coincidan con el
caso
patrón[0-9] *
) se omitirán (elcontinuar la declaración salta a la siguiente iteración del
for
loop). - Si lo deseas, puedes hacer algo más interesante aquí, por ejemplo. como omitir todos los archivos que no comienzan con una letra (a – z) usando[! a-z] *
, o puede usar múltiples patrones para omitir varios tipos de nombres de archivos, por ejemplo.[0-9] * | * .bak
para omitir los archivos.bak
y los archivos que no comienzan con un número.
esto lo haría excluyendo exactamente 'Música'
cp -a ^'Music' /target
esto y aquello para excluir cosas como Música? * o *? Música
cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target