bash - programas - ¿Cómo puedo usar comodines inversos o negativos cuando coincida un patrón en un shell de unix/linux?
programacion shell linux ejemplos (11)
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
/some/dir/*.txt
en todos los archivos de/some/dir
cuyo nombre termine con.txt
.En la línea dos se usa una declaración de caso 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 puedan coincidir solo con el nombre base del archivo. (Si solo está eliminando nombres de archivos basados en sufijos, puede acortar esto a$FILE
).En la línea tres, todos los archivos que coincidan con el patrón de
case
[0-9]*
) se omitirán (la instruccióncontinue
salta a la siguiente iteración del buclefor
). - Si lo desea, puede hacer algo más interesante aquí, por ejemplo, omitir todos los archivos que no comienzan con una letra (a – z) usando[!az]*
, o puede usar varios patrones para omitir varios tipos de nombres de archivos, por ejemplo[0-9]*|*.bak
para omitir tanto los archivos.bak
como los archivos que no comienzan con un número.
Digamos que quiero copiar el contenido de un directorio excluyendo 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?
En Bash puedes hacerlo habilitando la opción extglob
, como esta (reemplaza ls
con cp
y agrega el directorio de destino, por supuesto)
~/foobar> shopt -s extglob
~/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 puede deshabilitar extglob con
shopt -u extglob
En bash, una alternativa a shopt -s extglob
es la variable GLOBIGNORE
. No es realmente mejor, pero me resulta más fácil de recordar.
Un ejemplo que puede ser lo que quería el cartel original:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
Cuando unset GLOBIGNORE
, unset GLOBIGNORE
para poder rm *techno*
en el directorio de origen.
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
La opción de shell extglob
le brinda una coincidencia de patrones más poderosa en la línea de comandos.
Lo enciende con shopt -s extglob
y lo desactiva con shopt -u extglob
.
En tu ejemplo, inicialmente harías:
$ shopt -s extglob
$ cp !(*Music*) /target_directory
Los operadores de glob bing completos y de extremo ext. Disponibles son (extracto de man bash
):
Si la opción de shell extglob está habilitada utilizando el componente shopt, se reconocen varios operadores de coincidencia de patrones extendidos. En la siguiente descripción, una lista de patrones es una lista de uno o más patrones separados por un |. Los patrones compuestos se pueden formar 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- @ (lista de patrones)
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 shelling, por lo que el último ejemplo también podría escribirse como:
$ ls -d !(*.[ch])
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 llevar a cabo 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
No en bash (que yo sepa), sino en:
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 para
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/
También puedes usar un bucle bastante simple for
:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
Un truco que todavía no he visto aquí que no usa extglob
, find
o grep
es tratar dos listas de archivos como conjuntos y "dif" usarlas mediante comm
:
comm -23 <(ls) <(ls *Music*)
comm
es preferible sobre diff
porque no tiene crucero adicional.
Esto devuelve todos los elementos del conjunto 1, ls
, que no están 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 glob expansion, pero si está usando algo como find
, asegúrese de invocar el sort
.
comm -23 <(find . | sort) <(find . | grep -i ''.jpg'' | sort)
Potencialmente útil.
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
Find 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.