Tengo estas funciones en ~/.bashrc
:
function guard() {
if [ -e 'Gemfile' ]; then
bundle exec guard "$@"
else
command guard "$@"
fi
}
function rspec() {
if [ -e 'Gemfile' ]; then
bundle exec rspec "$@"
else
command rspec "$@"
fi
}
function rake() {
if [ -e 'Gemfile' ]; then
bundle exec rake "$@"
else
command rake "$@"
fi
}
Como ves estas funciones son muy similares. Quiero definir estas 3 funciones a la vez. ¿Hay alguna manera de hacerlo?
medio ambiente
bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
for loop?
quiero decir, las variables declaradas enfor loop
general generalmente desaparecen; esperaría lo mismo de las funciones por las mismas razones.bash -c 'for i in 1; do :; done; echo $i'
=>1
. Eltype
muestra claramente que las funciones existen fuera del alcance del bucle.bash
el alcance dinámico de todo lo que puede obtener es unalocal
variable local al alcance de una función completa , las variables definitivamente no "desaparecen" después de un ciclo. De hecho, dado que no hay una función involucrada aquí, ni siquiera es posible definir unalocal
variable en este caso.La voluntad anterior
. source /dev/fd/3
que se introduce en la_gem_dec()
función cada vez que se llama como unhere-document. _gem_dec's
único trabajo previamente evaluado es recibir un parámetro y evaluarlo previamente como elbundle exec
objetivo y como el nombre de la función en la que se dirige.NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
Sin embargo, en el caso anterior, no creo que pueda haber ningún riesgo.
Si el bloque de código anterior se copia en un
.bashrc
archivo, no solo las funciones de shell_guard(), _rspec()
y_rake()
se declararán al iniciar sesión, sino que la_gem_dec()
función también estará disponible para su ejecución en cualquier momento en el indicador de shell (o de otro modo) y así las nuevas funciones con plantilla pueden ser declarado cuando lo desee con solo:Y gracias a @Andrew por mostrarme que estos no serían comidos por un
for loop.
¿PERO CÓMO?
Sin embargo, utilizo el
3
descriptor de archivo anterior para mantenermestdin, stdout, and stderr, or <&0 >&1 >&2
abierto por hábito, como también es el caso de algunas de las otras precauciones predeterminadas que implemento aquí, porque la función resultante es tan simple que realmente no es necesaria. Sin embargo, es una buena práctica. Llamarshift $#
es otra de esas precauciones innecesarias.Aún así, cuando un archivo se especifica como
<input
o>output
con[optional num]<file
o[optional num]>file
redirección, el núcleo lo lee en un descriptor de archivo, al que se puede acceder a través de loscharacter device
archivos especiales en/dev/fd/[0-9]*
. Si[optional num]
se omite el especificador,0<file
se supone para entrada y1>file
salida. Considera esto:Y como a
here-document
es solo un medio para describir un archivo en línea dentro de un bloque de código, cuando lo hacemos:También podríamos hacer:
Con una distinción muy importante . Si no
"'\quote'"
lo<<"'\LIMITER"'
hace,here-document
entonces el shell lo evaluará para el shell$expansion
como:Entonces, para
_gem_dec()
, el3<<-FUNC here-document
se evalúa como un archivo en la entrada, de la misma manera que lo sería si lo fuera,3<~/some.file
excepto que debido a que dejamos elFUNC
limitador sin comillas, primero se evalúa para$expansion.
Lo importante sobre esto es que es entrada, lo que significa solo existe para_gem_dec(),
pero también se evalúa antes de_gem_dec()
que se ejecute la función porque nuestro shell tiene que leer y evaluar su$expansions
antes de entregarla como entrada.Hagamos
guard,
por ejemplo:Primero, el shell tiene que manejar la entrada, lo que significa leer:
En el descriptor de archivo 3 y evaluándolo para la expansión del shell. Si en este momento corriste:
O:
Como ambos son comandos equivalentes, verías *:
... antes de que se ejecute ningún código en la función. Esta es la función
<input
, después de todo. Para más ejemplos, vea mi respuesta a una pregunta diferente aquí .(* Técnicamente, esto no es del todo cierto. Debido a que utilizo una guía
-dash
antes de lahere-doc limiter
, todo lo anterior estaría justificado a la izquierda. Pero utilicé-dash
lo que pude<tab-insert>
para facilitar la lectura en primer lugar, así que no voy a quitar el<tab-inserts>
antes ofreciéndolo para leer ...)La mejor parte de esto es la cita: tenga en cuenta que las
'"
citas permanecen y solo las\
citas se eliminaron. Es probablemente por esta razón más que cualquier otra que si tiene que evaluar dos veces un shell$expansion
, lo recomendaréhere-document
porque las citas son mucho más fáciles queeval
.De todos modos, ahora el código anterior es exactamente como un archivo alimentado como
3<~/heredoc.file
simplemente esperando que la_gem_dec()
función se ponga en marcha y acepte su entrada/dev/fd/3
.Entonces, cuando comenzamos, lo
_gem_dec()
primero que hago es arrojar todos los parámetros posicionales, porque nuestro siguiente paso es una expansión de shell dos veces evaluada y no quiero que ninguno de los contenidos$expansions
se interprete como ninguno de mis$1 $2 $3...
parámetros actuales . Asique:shift
descarta tantospositional parameters
como especifiques y comienza$1
con lo que queda. Entonces, si llamé_gem_dec one two three
en la_gem_dec's $1 $2 $3
posición, los parámetros posicionales seríanone two three
y el recuento posicional actual total, o$#
sería 3. Si luego llamé ashift 2,
los valores deone
ytwo
seríashift
editado , el valor de$1
cambiaríathree
y$#
se expandiría a 1. Así queshift $#
solo los tira a todos a la basura. Hacer esto es estrictamente precautorio y es solo un hábito que he desarrollado después de hacer este tipo de cosas por un tiempo. Aquí está(subshell)
un poco extendido para mayor claridad:De todos modos, el siguiente paso es donde sucede la magia. Si
. ~/some.sh
en el indicador de comandos de la shell, todas las funciones y variables de entorno declaradas en~/some.sh
ese momento serían invocables en el indicador de shell. Lo mismo es cierto aquí, excepto que tenemos. source
elcharacter device
archivo especial para nuestro descriptor de archivo o. /dev/fd/3
, que es donde nuestrohere-document
archivo en línea ha sido modificado, y hemos declarado nuestra función. Y así es como funciona.Ahora hace lo
_guard
que se supone que debe hacer tu función.Apéndice:
Una excelente manera de decir guardar tus posicionales:
EDITAR:
Cuando respondí por primera vez a esta pregunta, me concentré más en el problema de declarar un caparazón
function()
capaz de declarar otras funciones que persistirían en el$ENV
planchado actual del caparazón que en lo que el autor de la pregunta haría con dichas funciones persistentes. Desde entonces me di cuenta de que mi solución original ofrecida en la que3<<-FUNC
tomó la forma:Probablemente no han funcionado como se esperaba para el autor de la pregunta porque Alteré específicamente el nombre de la función declarativa de
$1
a_${1}
que, de ser llamado así_gem_dec guard
por ejemplo, daría lugar a_gem_dec
declarar una función llamada_guard
en lugar de sóloguard
.Nota: Tal comportamiento es una cuestión de hábito para mí: normalmente opero bajo la presunción de que las funciones de shell deberían ocupar solo las suyas
_namespace
para evitar su intrusión en elnamespace
shellcommands
.Sin embargo, este no es un hábito universal, como se evidencia en el uso que hace el autor de la
command
llamada$1
.Un examen más detallado me lleva a creer lo siguiente:
Esto no habría funcionado anteriormente porque también alteré el
$1
llamado porcommand
leer:Lo que no habría resultado en la ejecución de la
ruby
función que la función de shell compiló como:Espero que puedan ver (como eventualmente lo hice) que parece que el autor de la pregunta solo está usando
command
en absoluto para especificar indirectamentenamespace
porquecommand
preferirá llamar a un archivo ejecutable$PATH
sobre una función de shell del mismo nombre.Si mi análisis es correcto (como espero que lo confirme) , esto:
Debería satisfacer mejor esas condiciones con la excepción de que llamar
guard
al indicador solo intentará ejecutar un archivo ejecutable en$PATH
namedguard
mientras que llamar_guard
al indicador verificará laGemfile's
existencia y compilará en consecuencia o ejecutará elguard
ejecutable en$PATH
. De esta maneranamespace
está protegido y, al menos como lo percibo, la intención del autor de la pregunta aún se cumple.De hecho, suponiendo que nuestra función de shell
_${1}()
y el ejecutable${PATH}/${1}
son las dos únicas formas en que nuestro shell podría interpretar una llamada a cualquiera$1
o_${1}
luego, el uso decommand
la función ahora se vuelve completamente redundante. Aún así, he dejado que permanezca, ya que no me gusta cometer el mismo error dos veces ... seguidas de todos modos.Si esto es inaceptable para el autor de la pregunta y él / ella preferiría eliminarlo por
_
completo, entonces, en su forma actual, editar la_underscore
salida debería ser todo lo que el solicitante debe hacer para cumplir con sus requisitos tal como los entiendo.Aparte de ese cambio, también he editado la función para usar
&&
y / o||
condicionales de cortocircuito de shell en lugar de laif/then
sintaxis original . De esta manera elcommand
comunicado sólo se evalúa en absoluto siGemfile
no está en$PATH
. Sin embargo, esta modificación requiere la adiciónreturn $?
para garantizar que labundle
instrucción no se ejecute en el caso deGemfile
que no exista, pero laruby $1
función devuelve algo que no sea 0.Por último, debo señalar que esta solución implementa solo construcciones de shell portátiles. En otras palabras, esto debería producir resultados idénticos en cualquier shell que reclame compatibilidad POSIX. Si bien, por supuesto, sería una tontería para mí afirmar que todos los sistemas compatibles con POSIX deben manejar la
ruby bundle
directiva, al menos los imperativos de shell que lo invocan deben comportarse de la misma manera, independientemente de si el shell de llamada essh
o nodash
. Además, lo anterior funcionará como se espera (suponiendo al menos medio sano deshopts
todos modos) en ambosbash
yzsh
.fuente
~/.bashrc
y llamo. ~/.bashrc
, luego se ejecutan estas tres funciones. Tal vez el comportamiento difiere según el entorno, así que agregué mi entorno a la pregunta. Además, no podía entender por qué_guard ; _rspec ; _rake
se necesita la última línea . Busqué sobreshift
el descriptor de archivos, parece que estos están más allá de mi comprensión actual.fuente