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 loopgeneral generalmente desaparecen; esperaría lo mismo de las funciones por las mismas razones.bash -c 'for i in 1; do :; done; echo $i'=>1. Eltypemuestra claramente que las funciones existen fuera del alcance del bucle.bashel alcance dinámico de todo lo que puede obtener es unalocalvariable 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 unalocalvariable en este caso.La voluntad anterior
. source /dev/fd/3que 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 execobjetivo 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
.bashrcarchivo, 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
3descriptor de archivo anterior para mantenermestdin, stdout, and stderr, or <&0 >&1 >&2abierto 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
<inputo>outputcon[optional num]<fileo[optional num]>fileredirección, el núcleo lo lee en un descriptor de archivo, al que se puede acceder a través de loscharacter devicearchivos especiales en/dev/fd/[0-9]*. Si[optional num]se omite el especificador,0<filese supone para entrada y1>filesalida. Considera esto:Y como a
here-documentes 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-documententonces el shell lo evaluará para el shell$expansioncomo:Entonces, para
_gem_dec(), el3<<-FUNC here-documentse evalúa como un archivo en la entrada, de la misma manera que lo sería si lo fuera,3<~/some.fileexcepto que debido a que dejamos elFUNClimitador 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$expansionsantes 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
-dashantes de lahere-doc limiter, todo lo anterior estaría justificado a la izquierda. Pero utilicé-dashlo 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-documentporque 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.filesimplemente 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$expansionsse interprete como ninguno de mis$1 $2 $3...parámetros actuales . Asique:shiftdescarta tantospositional parameterscomo especifiques y comienza$1con lo que queda. Entonces, si llamé_gem_dec one two threeen la_gem_dec's $1 $2 $3posición, los parámetros posicionales seríanone two threey el recuento posicional actual total, o$#sería 3. Si luego llamé ashift 2,los valores deoneytwoseríashifteditado , el valor de$1cambiaríathreey$#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.shen el indicador de comandos de la shell, todas las funciones y variables de entorno declaradas en~/some.shese momento serían invocables en el indicador de shell. Lo mismo es cierto aquí, excepto que tenemos. sourceelcharacter devicearchivo especial para nuestro descriptor de archivo o. /dev/fd/3, que es donde nuestrohere-documentarchivo en línea ha sido modificado, y hemos declarado nuestra función. Y así es como funciona.Ahora hace lo
_guardque 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$ENVplanchado 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<<-FUNCtomó 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
$1a_${1}que, de ser llamado así_gem_dec guardpor ejemplo, daría lugar a_gem_decdeclarar una función llamada_guarden 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
_namespacepara evitar su intrusión en elnamespaceshellcommands.Sin embargo, este no es un hábito universal, como se evidencia en el uso que hace el autor de la
commandllamada$1.Un examen más detallado me lleva a creer lo siguiente:
Esto no habría funcionado anteriormente porque también alteré el
$1llamado porcommandleer:Lo que no habría resultado en la ejecución de la
rubyfunció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
commanden absoluto para especificar indirectamentenamespaceporquecommandpreferirá llamar a un archivo ejecutable$PATHsobre 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
guardal indicador solo intentará ejecutar un archivo ejecutable en$PATHnamedguardmientras que llamar_guardal indicador verificará laGemfile'sexistencia y compilará en consecuencia o ejecutará elguardejecutable en$PATH. De esta maneranamespaceestá 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$1o_${1}luego, el uso decommandla 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_underscoresalida 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/thensintaxis original . De esta manera elcommandcomunicado sólo se evalúa en absoluto siGemfileno está en$PATH. Sin embargo, esta modificación requiere la adiciónreturn $?para garantizar que labundleinstrucción no se ejecute en el caso deGemfileque no exista, pero laruby $1funció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 bundledirectiva, al menos los imperativos de shell que lo invocan deben comportarse de la misma manera, independientemente de si el shell de llamada essho nodash. Además, lo anterior funcionará como se espera (suponiendo al menos medio sano deshoptstodos modos) en ambosbashyzsh.fuente
~/.bashrcy 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 ; _rakese necesita la última línea . Busqué sobreshiftel descriptor de archivos, parece que estos están más allá de mi comprensión actual.fuente