J'ai ces fonctions dans ~/.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
}
Comme vous le voyez, ces fonctions sont très similaires. Je veux définir ces 3 fonctions à la fois. Y a-t-il un moyen de le faire?
environnement
bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
for loop?
je veux dire, les variables déclarées dans unfor loop
disparaissent généralement - je m'attendrais à la même chose pour les mêmes fonctions.bash -c 'for i in 1; do :; done; echo $i'
=>1
. Letype
montre clairement que les fonctions existent en dehors de la portée de la boucle.bash
l'étendue dynamique de, tout ce que vous pouvez obtenir est unelocal
variable locale à la portée d'une fonction entière , les variables ne "disparaissent" certainement pas après une boucle. En fait, puisqu'il n'y a pas de fonction impliquée ici, il n'est même pas possible de définir unelocal
variable dans ce cas.La volonté ci-dessus
. source /dev/fd/3
qui est introduite dans la_gem_dec()
fonction chaque fois qu'elle est appelée commehere-document. _gem_dec's
tâche pré-évaluée uniquement doit recevoir un paramètre et la pré-évaluer à la fois commebundle exec
cible et comme nom de la fonction dans laquelle elle est ciblée.NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
Dans le cas ci-dessus cependant, je ne pense pas qu'il puisse y avoir de risque.
Si le bloc de code ci - dessus est copié dans un
.bashrc
fichier, non seulement les fonctions shell_guard(), _rspec()
et_rake()
être déclarés à la connexion, mais la_gem_dec()
fonction sera également disponible pour l' exécution à tout moment à votre invite du shell (ou autre) et ainsi de nouvelles fonctions templated peut être déclaré à tout moment avec:Et merci à @Andrew de m'avoir montré qu'ils ne seraient pas mangés par un
for loop.
MAIS COMMENT?
J'utilise le
3
descripteur de fichier ci-dessus pour resterstdin, stdout, and stderr, or <&0 >&1 >&2
ouvert par habitude - bien que, comme c'est également le cas pour quelques-unes des autres précautions par défaut que j'implémente ici - parce que la fonction résultante est si simple, ce n'est vraiment pas nécessaire. C'est cependant une bonne pratique. L'appelshift $#
est une autre de ces précautions inutiles.Pourtant, lorsqu'un fichier est spécifié comme
<input
ou>output
avec[optional num]<file
ou[optional num]>file
redirection du noyau , il se lit dans un descripteur de fichier, accessible via lescharacter device
fichiers spéciaux/dev/fd/[0-9]*
. Si le[optional num]
spécificateur est omis, il0<file
est supposé pour l'entrée et1>file
pour la sortie. Considère ceci:Et parce que a
here-document
n'est qu'un moyen de décrire un fichier en ligne dans un bloc de code, quand nous faisons:On pourrait aussi bien faire:
Avec une distinction très importante . Si vous ne faites pas
"'\quote'"
l'<<"'\LIMITER"'
unhere-document
puis l'interpréteur de commandes évaluer shell$expansion
comme:Donc, pour
_gem_dec()
, le3<<-FUNC here-document
est évalué comme un fichier en entrée, le même que s'il l'était,3<~/some.file
sauf que parce que nous laissons leFUNC
limiteur libre de guillemets, il est d'abord évalué pour$expansion.
L'important à ce sujet est qu'il est en entrée, ce qui signifie il n'existe que pour_gem_dec(),
mais il est également évalué avant l'_gem_dec()
exécution de la fonction car notre shell doit lire et évaluer son$expansions
avant de le transmettre en entrée.Permet
guard,
par exemple:Donc, le shell doit d'abord gérer l'entrée, ce qui signifie lire:
Dans le descripteur de fichier 3 et en l'évaluant pour l'expansion du shell. Si à ce moment vous avez couru:
Ou:
Comme ce sont deux commandes équivalentes, vous verriez *:
... avant tout, aucun code de la fonction ne s'exécute. C'est la fonction
<input
, après tout. Pour plus d'exemples, voir ma réponse à une autre question ici .(* Techniquement ce n'est pas tout à fait vrai. Parce que j'utilise un chef de file
-dash
avanthere-doc limiter
, le serait surtout justifié à gauche. Mais je l'-dash
que je puisse<tab-insert>
pour une meilleure lisibilité en premier lieu , donc je ne vais pas dépouiller les<tab-inserts>
avant en vous l'offrant à lire ...)La plus belle partie à ce sujet est la citation - notez que les
'"
citations restent et que seules les\
citations ont été supprimées. C'est probablement pour cette raison plus que toute autre que si vous devez évaluer deux fois un shell,$expansion
je recommanderai lehere-document
car les citations sont beaucoup plus faciles queeval
.Quoi qu'il en soit, le code ci-dessus est maintenant exactement comme un fichier alimenté, comme
3<~/heredoc.file
s'il attendait que la_gem_dec()
fonction démarre et accepte son entrée/dev/fd/3
.Donc, lorsque nous commençons,
_gem_dec()
la première chose que je fais est de lancer tous les paramètres positionnels, car notre prochaine étape est une expansion du shell évaluée deux fois et je ne veux pas que le contenu$expansions
soit interprété comme l'un de mes$1 $2 $3...
paramètres actuels . Donc je:shift
jette autantpositional parameters
que vous spécifiez et commence$1
avec ce qui reste. Donc , si j'appelé_gem_dec one two three
à l'invite des_gem_dec's $1 $2 $3
paramètres de position serait êtreone two three
et le nombre total actuel de position, ou$#
serait 3. Si j'ai appelé alorsshift 2,
les valeurs deone
ettwo
seraisshift
ed loin, la valeur$1
changerait àthree
et$#
élargirais à 1. Alorsshift $#
que les jette tous. Faire cela est strictement préventif et n'est qu'une habitude que j'ai développée après avoir fait ce genre de chose pendant un certain temps. Le voici(subshell)
un peu étalé pour plus de clarté:Quoi qu'il en soit, la prochaine étape est celle où la magie opère. Si vous
. ~/some.sh
à l'invite du shell, toutes les fonctions et variables d'environnement déclarées dans~/some.sh
seront alors appelables à votre invite du shell. La même chose est vraie ici, sauf que nous. source
lecharacter device
fichier spécial pour notre descripteur de fichier, ou. /dev/fd/3
- qui est où notrehere-document
fichier en ligne a été tracé - et nous avons déclaré notre fonction. Et c'est comme ça que ça marche.Fait maintenant tout ce que votre
_guard
fonction est censée faire.Addenda:
Une excellente façon de dire enregistrez vos positions:
ÉDITER:
Lorsque j'ai répondu à cette question pour la première fois, je me suis davantage concentré sur le problème de la déclaration d'un shell
function()
capable de déclarer d'autres fonctions qui persisteraient dans le$ENV
repassage actuel du shell que sur ce que le demandeur ferait avec lesdites fonctions persistantes. Depuis lors, je me suis rendu compte que ma solution initialement proposée offrait3<<-FUNC
la forme suivante:N'aurait probablement pas fonctionné comme prévu pour le demandeur, car j'ai spécifiquement modifié le nom de la fonction déclarative
$1
à partir de_${1}
laquelle, s'il était appelé comme_gem_dec guard
par exemple, entraînerait la_gem_dec
déclaration d'une fonction nommée_guard
par opposition à justeguard
.Remarque: Un tel comportement est une question d'habitude pour moi - j'opère généralement sur la présomption que les fonctions du shell ne devraient occuper que les leurs
_namespace
afin d'éviter leur intrusion dans lenamespace
shellcommands
proprement dit.Ce n'est pas une habitude universelle, cependant, comme en témoigne l'utilisation du demandeur d'
command
appeler$1
.Un examen plus approfondi me porte à croire ce qui suit:
Cela n'aurait pas fonctionné auparavant parce que j'ai également modifié le
$1
appelé parcommand
pour lire:Ce qui n'aurait pas entraîné l'exécution de la
ruby
fonction que la fonction shell a compilée comme suit:J'espère que vous pouvez voir (comme finalement je l'ai fait) qu'il semble que le demandeur n'utilise que
command
du tout pour spécifier indirectementnamespace
carcommand
préférera appeler un fichier exécutable$PATH
sur une fonction shell du même nom.Si mon analyse est correcte (comme j'espère que le demandeur le confirmera), alors ceci:
Devrait mieux satisfaire ces conditions à l'exception que l'appel
guard
à l'invite tentera uniquement d'exécuter un fichier exécutable dans$PATH
namedguard
alors que l'appel_guard
à l'invite vérifiera l'Gemfile's
existence et compilera en conséquence ou exécutera l'guard
exécutable dans$PATH
. De cette manièrenamespace
est protégé et, au moins comme je le perçois, l'intention du demandeur est toujours remplie.En fait, en supposant que notre fonction shell
_${1}()
et l'exécutable${PATH}/${1}
sont les deux seules façons dont notre shell pourrait interpréter un appel à l'un$1
ou l' autre ou_${1}
alors l'utilisation decommand
la fonction est désormais entièrement redondante. Pourtant, je l'ai laissé rester car je n'aime pas faire la même erreur deux fois ... d'affilée de toute façon.Si cela est inacceptable pour le demandeur et qu'il / elle préférerait supprimer
_
complètement le contenu, alors, dans sa forme actuelle, la modification_underscore
devrait être tout ce que le demandeur doit faire pour répondre à ses besoins tels que je les comprends.Mis à part ce changement, j'ai également modifié la fonction pour utiliser
&&
et / ou||
les conditions de court-circuit du shell plutôt que laif/then
syntaxe d' origine . De cette façon , lacommand
déclaration est évaluée uniquement du tout siGemfile
c'est pas$PATH
. Cette modification nécessite toutefois l'ajout dereturn $?
pour garantir que l'bundle
instruction n'est pas exécutée si l'événementGemfile
n'existe pas, mais laruby $1
fonction renvoie autre chose que 0.Enfin, je dois noter que cette solution n'implémente que des constructions de shell portables. En d'autres termes, cela devrait produire des résultats identiques dans tout shell revendiquant la compatibilité POSIX. Bien qu'il serait, bien sûr, absurde pour moi de prétendre que tous les systèmes compatibles POSIX doivent gérer la
ruby bundle
directive, au moins les impératifs du shell qui l'appelle devraient se comporter de la même manière, que le shell appelant soitsh
oudash
. De plus, ce qui précède fonctionnera comme prévu (en supposant au moins à mi-chemin deshopts
toute façon) dans les deuxbash
etzsh
.la source
~/.bashrc
et j'appelle. ~/.bashrc
, puis ces trois fonctions sont exécutées. Peut-être que le comportement diffère selon l'environnement, j'ai donc ajouté mon environnement à la question. De plus, je ne comprenais pas pourquoi la dernière ligne_guard ; _rspec ; _rake
était nécessaire. J'ai recherchéshift
et descripteur de fichier, il semble que cela dépasse ma compréhension actuelle.la source