Ich habe diese Funktionen in ~/.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
}
Wie Sie sehen, sind diese Funktionen sehr ähnlich. Ich möchte diese 3 Funktionen gleichzeitig definieren. Gibt es einen Weg, es zu schaffen?
Umgebung
bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
for loop?
ich meine, Variablen, die in einemfor loop
allgemein deklariert sind, verschwinden - ich würde das gleiche von Funktionen aus den gleichen Gründen erwarten.bash -c 'for i in 1; do :; done; echo $i'
=>1
. Dastype
zeigt deutlich, dass die Funktionen außerhalb des Bereichs der Schleife existieren.bash
dem dynamischen Scoping können Sie nur einelocal
Variable erhalten, die lokal für den Umfang einer ganzen Funktion ist. Variablen "verschwinden" definitiv nicht nach einer Schleife. Da hier keine Funktion beteiligt ist, ist eslocal
in diesem Fall nicht einmal möglich, eine Variable zu definieren .Der obige Wille,
. source /dev/fd/3
der der_gem_dec()
Funktion jedes Mal zugeführt wird, wenn sie als vorab ausgewerteterhere-document. _gem_dec's
Job aufgerufen wird, besteht darin, einen Parameter zu empfangen und ihn sowohl alsbundle exec
Ziel als auch als Namen der Funktion vorab auszuwerten, auf die er abzielt.NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
Im obigen Fall glaube ich jedoch nicht, dass ein Risiko bestehen kann.
Wenn der obige Codeblock in eine
.bashrc
Datei kopiert wird, werden nicht nur die Shell-Funktionen_guard(), _rspec()
und_rake()
beim Anmelden deklariert, sondern die_gem_dec()
Funktion kann auch jederzeit an Ihrer Shell-Eingabeaufforderung (oder auf andere Weise) ausgeführt werden, sodass neue Vorlagenfunktionen verfügbar sind deklariert werden, wann immer Sie möchten mit nur:Und danke an @Andrew, der mir gezeigt hat, dass diese nicht von einem gefressen werden
for loop.
ABER WIE?
Ich verwende den
3
obigen Dateideskriptor, um michstdin, stdout, and stderr, or <&0 >&1 >&2
aus Gewohnheit offen zu halten - wie es auch bei einigen anderen Standardvorkehrungen der Fall ist, die ich hier implementiere -, weil die resultierende Funktion so einfach ist, dass sie wirklich nicht notwendig ist. Es ist jedoch eine gute Praxis. Das Anrufenshift $#
ist eine weitere dieser unnötigen Vorsichtsmaßnahmen.Wenn eine Datei als
<input
oder>output
mit[optional num]<file
oder[optional num]>file
umgeleitet wird, liest der Kernel sie in einen Dateideskriptor ein, auf den über diecharacter device
speziellen Dateien in zugegriffen werden kann/dev/fd/[0-9]*
. Wenn der Bezeichner[optional num]
weggelassen wird,0<file
wird für die Eingabe und1>file
für die Ausgabe angenommen. Bedenken Sie:Und weil a
here-document
nur ein Mittel ist, um eine Datei in einem Codeblock inline zu beschreiben, wenn wir Folgendes tun:Wir könnten genauso gut tun:
Mit einem sehr wichtigen Unterschied. Wenn nicht
"'\quote'"
die<<"'\LIMITER"'
eineshere-document
dann wird die Schale für Schale bewerten$expansion
wie:Also, für
_gem_dec()
, die3<<-FUNC here-document
als Datei auf Eingabe ausgewertet wird, gleich wie es wäre, wenn es waren3<~/some.file
außer dass , weil wir das verlassenFUNC
Limiter frei von Kursen, zunächst für ausgewertet$expansion.
Das Wichtigste dabei ist , dass es eingegeben wird, Bedeutung Es existiert nur für,_gem_dec(),
aber es wird auch ausgewertet, bevor die_gem_dec()
Funktion ausgeführt wird, da unsere Shell sie lesen und auswerten muss,$expansions
bevor sie als Eingabe übergeben wird.Machen wir
guard,
zum Beispiel:Also muss die Shell zuerst die Eingabe verarbeiten, was bedeutet, dass sie liest:
In Dateideskriptor 3 und Auswertung für die Shell-Erweiterung. Wenn Sie zu diesem Zeitpunkt gelaufen sind:
Oder:
Da beide äquivalente Befehle sind, sehen Sie *:
... bevor überhaupt ein Code in der Funktion ausgeführt wird. Dies ist die Funktion des
<input
, nachdem alle. Weitere Beispiele finden Sie in meiner Antwort auf eine andere Frage hier .(* Technisch ist dies nicht ganz richtig. Weil ich ein führender verwenden ,
-dash
bevor dashere-doc limiter
würde das vor allem linksbündig werden. Aber ich habe das-dash
so konnte ich<tab-insert>
in erster Linie für die Lesbarkeit so werde ich das nicht strippen<tab-inserts>
vor Biete es dir zum Lesen an ...)Das Schönste daran ist das Zitieren - beachten Sie, dass die
'"
Anführungszeichen erhalten bleiben und nur die\
Anführungszeichen entfernt wurden. Es ist wahrscheinlich mehr als jeder andere Grund, dass$expansion
ich das empfehlen werde , wenn Sie eine Shell zweimal bewerten müssen,here-document
da die Anführungszeichen viel einfacher sind alseval
.Wie auch immer, jetzt ist der obige Code genau wie eine eingespeiste Datei, als würde man
3<~/heredoc.file
nur darauf warten, dass die_gem_dec()
Funktion startet und ihre Eingabe akzeptiert/dev/fd/3
.Wenn wir also anfangen,
_gem_dec()
werfe ich als erstes alle Positionsparameter, da unser nächster Schritt eine zweimal evaluierte Shell-Erweiterung ist und ich nicht möchte, dass einer der enthaltenen Parameter$expansions
als einer meiner aktuellen$1 $2 $3...
Parameter interpretiert wird. Also ich:shift
verwirft so viele,positional parameters
wie Sie angeben, und beginnt$1
mit dem, was übrig bleibt. Also , wenn ich rief_gem_dec one two three
an der Eingabeaufforderung_gem_dec's $1 $2 $3
Positionsparameter seione two three
und die Gesamtstrom Positionsanzahl oder$#
3 sein würde , wenn ich rufe dannshift 2,
die Werteone
undtwo
würdeshift
ed weg, den Wert$1
ändern würdethree
und$#
würde erweitern , um 1. Soshift $#
einfach wirft sie alle weg. Dies zu tun ist streng vorsorglich und nur eine Gewohnheit, die ich entwickelt habe, nachdem ich so etwas für eine Weile getan habe. Hier ist es der(subshell)
Klarheit halber etwas ausgebreitet:Wie auch immer, der nächste Schritt ist, wo die Magie passiert. Wenn Sie sich
. ~/some.sh
an der Shell-Eingabeaufforderung befinden, können alle in deklarierten Funktionen und Umgebungsvariablen~/some.sh
an Ihrer Shell-Eingabeaufforderung aufgerufen werden. Das Gleiche gilt hier, außer dass wir. source
diecharacter device
spezielle Datei für unseren Dateideskriptor oder. /dev/fd/3
- wo unserehere-document
Inline-Datei gespeichert wurde - und unsere Funktion deklariert haben. Und so funktioniert es.Jetzt macht alles, was Ihre
_guard
Funktion tun soll.Nachtrag:
Eine gute Möglichkeit zu sagen, speichern Sie Ihre Positionals:
BEARBEITEN:
Als ich diese Frage zum ersten Mal beantwortete, konzentrierte ich mich mehr auf das Problem, eine Shell
function()
zu deklarieren, die andere Funktionen deklarieren kann, die in der aktuellen Shell-$ENV
Bügelung bestehen bleiben, als darauf, was der Fragesteller mit diesen persistenten Funktionen tun würde. Seitdem habe ich festgestellt, dass meine ursprünglich angebotene Lösung3<<-FUNC
die Form hatte:Hätte wahrscheinlich nicht gearbeitet wie für die Fragesteller erwartet , weil ich die deklarativen Funktion Namen von speziell verändert
$1
zu_${1}
denen, wenn wie genannt_gem_dec guard
zum Beispiel würde in der_gem_dec
Deklaration eine Funktion namens_guard
im Gegensatz zu nurguard
.Hinweis: Ein solches Verhalten ist für mich eine Gewohnheit - ich gehe normalerweise davon aus, dass Shell-Funktionen nur ihre eigenenFunktionen einnehmen sollten
_namespace
, um ein Eindringen in dieeigentlichenamespace
Shell zuvermeidencommands
.Dies ist jedoch keine universelle Gewohnheit, wie aus der Verwendung des Fragestellers
command
zum Anrufen hervorgeht$1
.Eine weitere Untersuchung lässt mich Folgendes glauben:
Das hätte vorher nicht geklappt, weil ich auch den
$1
voncommand
gelesenen geändert habe:Was nicht zur Ausführung der
ruby
Funktion geführt hätte, die die Shell-Funktion kompiliert hat als:Ich hoffe, Sie können sehen (wie ich es letztendlich getan habe), dass der Fragesteller anscheinend nur
command
indirekt spezifiziert,namespace
weil ercommand
es vorzieht, eine ausführbare Datei$PATH
über eine gleichnamige Shell-Funktion aufzurufen .Wenn meine Analyse korrekt ist (wie ich hoffe, dass der Fragesteller dies bestätigt), dann ist dies:
Sollte diese Bedingungen besser erfüllen, mit der Ausnahme, dass beim Aufrufen
guard
der Eingabeaufforderung nur versucht wird, eine ausführbare Datei in$PATH
named auszuführen,guard
während beim Aufrufen_guard
der Eingabeaufforderung dieGemfile's
Existenz überprüft und entsprechend kompiliert oder dieguard
ausführbare Datei in ausgeführt wird$PATH
. Auf diese Weisenamespace
wird geschützt und zumindest so, wie ich es wahrnehme, wird die Absicht des Fragestellers immer noch erfüllt.In der Tat, unsere Shell - Funktion vorausgesetzt ,
_${1}()
und die ausführbaren Datei${PATH}/${1}
sind die nur zwei Möglichkeiten , um unsere Schale einen Anruf entweder interpretieren könnte$1
oder_${1}
dann die Verwendung voncommand
in der Funktion überhaupt völlig überflüssig gemacht wird nun. Trotzdem habe ich es bleiben lassen, da ich nicht zweimal den gleichen Fehler machen möchte ... sowieso hintereinander.Wenn dies für den Fragesteller nicht akzeptabel ist und er / sie es vorziehen würde, das Problem
_
vollständig_underscore
zu beseitigen, sollte in der aktuellen Form die Bearbeitung des Outs alles sein, was der Fragesteller tun muss, um seine / ihre Anforderungen zu erfüllen, so wie ich sie verstehe.Abgesehen von dieser Änderung habe ich bearbeitet auch die Funktion zu verwenden
&&
und / oder||
Schale Kurzschluss conditionals anstatt die ursprünglicheif/then
Syntax. Auf diese Art und Weise diecommand
Anweisung wird nur dann ausgewertet , überhaupt , wennGemfile
nicht in ist$PATH
. Diese Änderung erfordert jedoch das Hinzufügen von,return $?
um sicherzustellen, dass diebundle
Anweisung nicht ausgeführt wird, falls das EreignisGemfile
nicht vorhanden ist, dieruby $1
Funktion jedoch etwas anderes als 0 zurückgibt .Zuletzt sollte ich beachten, dass diese Lösung nur tragbare Shell-Konstrukte implementiert. Mit anderen Worten, dies sollte zu identischen Ergebnissen in jeder Shell führen, die POSIX-Kompatibilität beansprucht. Während es für mich natürlich Unsinn wäre zu behaupten, dass jedes POSIX-kompatible System mit der
ruby bundle
Direktive umgehen muss , sollten sich zumindest die Shell-Imperative, die es aufrufen, gleich verhalten, unabhängig davon, ob die aufrufende Shellsh
oder istdash
. Auch das oben Genannteshopts
wird in beiden und wie erwartet funktionieren (vorausgesetzt, es ist sowieso mindestens halbwegs gesund ) .bash
zsh
quelle
~/.bashrc
und rufe auf. ~/.bashrc
, dann werden diese drei Funktionen ausgeführt. Vielleicht unterscheidet sich das Verhalten je nach Umgebung, deshalb habe ich der Frage meine Umgebung hinzugefügt. Außerdem konnte ich nicht verstehen, warum die letzte Zeile_guard ; _rspec ; _rake
benötigt wird. Ich habe nachshift
und Dateideskriptor gesucht , es sieht so aus, als ob diese über mein derzeitiges Verständnis hinausgehen.quelle