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
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 usareval
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 llamareval
que está buscando problemas). ‘ desearía que el globbing esté habilitado parafor i in $dir
yfor l in $k
, pero no parafor j in $exclude
. Usted ‘ desearía unset -f
antes de este último y unset +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 paraecho $l
, entonces$l
debe citarse allí. - @St é phaneChazelas ¿se está refiriendo a v1 o v2? Para la versión 2, tanto
exclude
ydirs
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=*
yfoo='*'
es igual. Peroecho $foo
yecho "$foo"
no lo son (en shells comobash
, ‘ 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 enzsh
, 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
k
es una cadena calculada, y la necesito sta y de esa manera hasta el bucle. Por favor revise mi versión larga.