¿Cómo puedo usar comodines inversos o negativos cuando coincida un patrón en un shell de unix / linux?

StackOverflow https://stackoverflow.com/questions/216995

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?

¿Fue útil?

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
  1. En la línea uno, el patrón /some/dir/*.txt hará que el bucle for se repita en todos los archivos en / some / dir cuyo nombre termina con .txt.

  2. 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).

  3. En la línea tres, todos los archivos que coincidan con el caso patrón [0-9] * ) se omitirán (el continuar 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top