¿Cómo hacer que bash glob sea una variable de cadena?

Información del sistema

SO: OS X

bash: GNU bash, versión 3.2.57 (1) -release (x86_64-apple-darwin16)

Antecedentes

Quiero que Time Machine excluya un conjunto de directorios y archivos de todo mi proyecto git / nodejs. Los directorios de mi proyecto están en ~/code/private/ y ~/code/public/ , así que estoy intentando use bash looping para hacer el tmutil.

Problema

Versión corta

Si tengo un calculada variable de cadena k, ¿cómo puedo hacer que se glob dentro o justo antes de una for- loop:

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" for i in $k # I need $k to glob here do echo $i done 

En la versión larga a continuación, verá k=$i/$j. Así que no puedo codificar la cadena en el bucle for.

Versión larga

#!/bin/bash exclude=" *.launch .classpath .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" ~/code/private/* ~/code/public/* " for i in $dirs do for j in $exclude do k=$i/$j # It is correct up to this line for l in $k # I need it glob here do echo $l # Command I want to execute # tmutil addexclusion $l done done done 

Salida

No son globled. No es lo que quiero.

~/code/private/*/*.launch ~/code/private/*/.DS_Store ~/code/private/*/.classpath ~/code/private/*/.sass-cache ~/code/private/*/.settings ~/code/private/*/Thumbs.db ~/code/private/*/bower_components ~/code/private/*/build ~/code/private/*/connect.lock ~/code/private/*/coverage ~/code/private/*/dist ~/code/private/*/e2e/*.js ~/code/private/*/e2e/*.map ~/code/private/*/libpeerconnection.log ~/code/private/*/node_modules ~/code/private/*/npm-debug.log ~/code/private/*/testem.log ~/code/private/*/tmp ~/code/private/*/typings ~/code/public/*/*.launch ~/code/public/*/.DS_Store ~/code/public/*/.classpath ~/code/public/*/.sass-cache ~/code/public/*/.settings ~/code/public/*/Thumbs.db ~/code/public/*/bower_components ~/code/public/*/build ~/code/public/*/connect.lock ~/code/public/*/coverage ~/code/public/*/dist ~/code/public/*/e2e/*.js ~/code/public/*/e2e/*.map ~/code/public/*/libpeerconnection.log ~/code/public/*/node_modules ~/code/public/*/npm-debug.log ~/code/public/*/testem.log ~/code/public/*/tmp ~/code/public/*/typings 

Comentarios

  • Las comillas simples detienen la interpolación de shell en Bash, por lo que puede intentar citar dos veces su variable.
  • @ThomasN no, eso no funciona. k es una cadena calculada, y la necesito sta y de esa manera hasta el bucle. Por favor revise mi versión larga.
  • @ThomasN Actualicé la versión corta para que sea más clara.

Responder

Puede forzar otra ronda de evaluación con eval, pero eso» no es realmente necesario. (Y eval comienza tiene serios problemas en el momento en que los nombres de sus archivos contienen caracteres especiales como $.) El problema no es con el globbing, sino con la expansión de tilde.

El globbing ocurre después de expansión de variables, si la variable no está entre comillas, como aquí (*) :

$ x="/tm*" ; echo $x /tmp 

Entonces, en la misma línea, esto es similar a lo que hiciste, y funciona:

$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch $ i="$HOME/public/*"; j="*.launch"; k="$i/$j" $ echo $k /home/foo/public/foo/x.launch 

Pero con la tilde no «t:

$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch 

Esto está claramente documentado para Bash:

El orden de las expansiones es: expansión de llaves; expansión de tilde, expansión de parámetros y variables, …

La expansión de tilde ocurre antes de la expansión de variable, por lo que las tildes dentro de las variables no se expanden. La solución fácil es usar $HOME o la ruta completa en su lugar.

(* expandir globs a partir de variables generalmente no es lo que desea)


Otra cosa:

Cuando recorre los patrones, como aquí:

exclude="foo *bar" for j in $exclude ; do ... 

tenga en cuenta que como $exclude no está entre comillas, se divide y también se engloba en este punto. Por lo tanto, si el directorio actual contiene algo que coincide con el patrón, se expande a eso:

$ i="$HOME/public/foo" $ exclude="*.launch" $ touch $i/real.launch $ for j in $exclude ; do # split and glob, no match echo "$i"/$j ; done /home/foo/public/foo/real.launch $ touch ./hello.launch $ for j in $exclude ; do # split and glob, matches in current dir! echo "$i"/$j ; done /home/foo/public/foo/hello.launch # not the expected result 

Para solucionar este problema, utilice una variable de matriz en lugar de una cadena dividida:

$ exclude=("*.launch") $ exclude+=("something else") $ for j in "${exclude[@]}" ; do echo "$i"/$j ; done /home/foo/public/foo/real.launch /home/foo/public/foo/something else 

Como beneficio adicional, las entradas de la matriz también pueden contener espacios en blanco sin problemas con la división.


Se podría hacer algo similar con find -path, si no le importa el nivel de directorio que deben tener los archivos de destino. Por ejemplo, para encontrar cualquier ruta que termine en /e2e/*.js:

$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js 

Tenemos que usar $HOME en lugar de ~ por la misma razón que antes, y $dirs debe estar sin comillas en el find línea de comando para que se divida, pero $pattern debe estar entre comillas para que no se expanda accidentalmente por el shell.

(Creo que podrías jugar con -maxdepth en GNU find para limitar la profundidad de la búsqueda, si te importa, pero ese es un problema un poco diferente).

Comentarios

  • ¿Eres la única respuesta con find? De hecho, también estoy explorando esa ruta, ya que el bucle for se está complicando. Pero tengo dificultades con la ‘ -path ‘.
  • Se le reconoce como su información sobre tilde ‘ ~ ‘ es más directo al problema principal. Publicaré el guión final y la explicación en otra respuesta. Pero todo el mérito para ti: D
  • @JohnSiu, sí, lo primero que me vino a la mente fue el uso de buscar. También podría ser utilizable, dependiendo de la necesidad exacta. (o mejor también, para algunos usos).
  • @kevinarpe, creo que las matrices están diseñadas básicamente para eso, y sí, "${array[@]}" (¡con las comillas! ) está documentado (consulte aquí y aquí ) para expandirse a los elementos como palabras distintas sin dividiéndolos aún más.
  • @sixtyfive, bueno, [abc] es una parte estándar de patrones globales , como ?, no ‘ no creo que ‘ sea necesario cubrirlos todos aquí .

Respuesta

Puede guardarlo como una matriz en lugar de una cadena para usar más tarde en muchos casos y deje que suceda el globbing cuando lo defina. En su caso, por ejemplo:

 k=(~/code/public/*/*.launch) for i in "${k[@]}"; do  

o en el ejemplo posterior, «Necesitaré eval algunas de las cadenas

 dirs=(~/code/private/* ~/code/public/*) for i in "${dirs[@]}"; do for j in $exclude; do eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done" done done  

Comentarios

  • Observe cómo $exclude contiene comodines, usted ‘ d necesita deshabilitar el globbing antes de usar el operador split + glob en él y restaurarlo para $i/$j y no usar eval pero use "$i"/$j
  • Tanto usted como ilkkachu dan una buena respuesta. Sin embargo, su respuesta identificó el problema. para él.

Respuesta

La respuesta de @ilkkachu resolvió el problema principal del globbing. Todo el crédito para él.

V1

Sin embargo, debido a que exclude contiene entradas con y sin comodín (*), y también es posible que no existan En total, se necesita una verificación adicional después del globbing de $i/$j. Estoy compartiendo mis hallazgos aquí.

#!/bin/bash exclude=" *.launch .DS_Store .classpath .sass-cache .settings Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " # loop $dirs for i in $dirs; do for j in $exclude ; do for k in $i/$j; do echo -e "$k" if [ -f $k ] || [ -d $k ] ; then # Only execute command if dir/file exist echo -e "\t^^^ Above file/dir exist! ^^^" fi done done done 

Explicación de salida

A continuación se muestra la salida parcial para explicar la situación.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store ^^^ Above file/dir exist! ^^^ 

Lo anterior se explica por sí mismo.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist 

Lo anterior aparece porque la entrada de exclusión ($j) no tiene comodines, $i/$j se convierte en una concatenación de cadena simple. Sin embargo, el archivo / directorio no existe.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map 

Lo anterior se muestra como entrada de exclusión ($j) contiene comodín pero no tiene coincidencia de archivo / directorio, el globbing de $i/$j simplemente devuelve la cadena original.

V2

V2 usa comillas simples, eval y shopt -s nullglob para obtener un resultado limpio. No se requiere verificación final de archivo / directorio.

#!/bin/bash exclude=" *.launch .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " for i in $dirs; do for j in $exclude ; do shopt -s nullglob eval "k=$i/$j" for l in $k; do echo $l done shopt -u nullglob done done 

Comentarios

  • Un problema es que en for j in $exclude, los globos en $exclude podrían expandirse en el momento de esa $exclude expansión (y llamar eval que está buscando problemas). ‘ desearía que el globbing esté habilitado para for i in $dir y for l in $k, pero no para for j in $exclude. Usted ‘ desearía un set -f antes de este último y un set +f para el otro. De manera más general, ‘ desearía ajustar su operador split + glob antes de usarlo. En cualquier caso, no ‘ t quiere split + glob para echo $l, entonces $l debe citarse allí.
  • @St é phaneChazelas ¿se está refiriendo a v1 o v2? Para la versión 2, tanto exclude y dirs están entre comillas simples (), so no globbing till eval`.
  • El globbing tiene lugar sobre la expansión de la variable sin comillas en contextos de lista , eso (dejando una variable sin comillas) es lo que a veces se llama al operador split + glob . No hay ‘ globbing en las asignaciones a variables escalares. foo=* y foo='*' es igual. Pero echo $foo y echo "$foo" no lo son (en shells como bash, ‘ se ha corregido en shells como zsh, fish o rc, consulte también el enlace anterior). Aquí hacer quiero usar ese operador, pero en algunos lugares solo la parte dividida y en otros solo la parte glob.
  • @St é phaneChazelas Gracias por la información !!! Me tomó algún tiempo, pero ahora entiendo la preocupación. ¡¡¡Esto es muy valioso !! ¡Gracias !!! l>

Responda

Con zsh:

 exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }  

${^array}string se expandirá como $array[1]string $array[2]string.... $=var es realizar la división de palabras en la variable (¡algo que otros shells hacen de forma predeterminada!), $~var hace globbing en la variable (algo diferente shells también de forma predeterminada (cuando generalmente no quiere que lo hagan, habría tenido que citar $f arriba en otros shells)).

(N) es un calificador glob que activa nullglob para cada uno de esos globs resultantes de ese $^array1/$^array2 expansión. Eso hace que los globos se expandan a nada cuando no coinciden. Eso también convierte un elemento no glob como ~/code/private/foo/Thumbs.db en uno, lo que significa que si ese particular no existe, no está incluido.

Comentarios

  • Esto es realmente bueno. Lo probé y funciona. Sin embargo, parece que zsh es más sensible a la nueva línea cuando usando comillas simples. La forma en que se encierra exclude está afectando la salida.
  • @JohnSiu, oh sí, ‘ correcto. Parece que el split + glob y el $^array deben realizarse en dos pasos separados para asegurarse de que los elementos vacíos se descarten (ver edición). Eso se parece un poco a un error en zsh, yo ‘ plantearé el problema en su lista de correo.
  • Se me ocurrió una versión 2 para bash, que es más limpio, pero aún no tan compacto como su script zsh, jejeje

Respuesta

Antiguo publicar pero tropecé aquí, así que t aunque esto puede ayudar a otros 🙂

Hay una función un poco-glob () en bash … se llama compgen -G Genera un valor por línea, por lo que necesita un readarray para funcionar.

Pruebe esto:

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" readarray -t files < <(compgen -G "$k") # $k is globbed here for i in "${files[@]}" do echo "Found -$i-" done 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *