_gem_dec() { shift $# ; . /dev/fd/3
} 3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
for func in guard rspec rake ; do _gem_dec $func ; done
echo "_guard ; _rspec ; _rake are all functions now."
Der obige Wille, . source /dev/fd/3
der der _gem_dec()
Funktion jedes Mal zugeführt wird, wenn sie als vorab ausgewerteter here-document. _gem_dec's
Job aufgerufen wird, besteht darin, einen Parameter zu empfangen und ihn sowohl als bundle 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:
_gem_dec $new_templated_function_name
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 mich stdin, 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 Anrufen shift $#
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 die character 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 und 1>file
für die Ausgabe angenommen. Bedenken Sie:
l='line %d\n' ; printf "$l" 1 2 3 4 5 6 >/dev/fd/1
> line 1
> line 2
> line 3
> line 4
> line 5
> line 6
( printf "$l" 4 5 6 >/dev/fd/3 ; printf "$l" 1 2 3 ) >/tmp/sample 3>/tmp/sample2
( cat /tmp/sample2 ) </tmp/sample
> line 4
> line 5
> line 6
( cat /dev/fd/0 ) </tmp/sample
> line 1
> line 2
> line 3
( cat /dev/fd/3 ) </tmp/sample 3</tmp/sample2
> line 4
> line 5
> line 6
Und weil a here-document
nur ein Mittel ist, um eine Datei in einem Codeblock inline zu beschreiben, wenn wir Folgendes tun:
<<'HEREDOC'
[$CODE]
HEREDOC
Wir könnten genauso gut tun:
echo '[$CODE]' >/dev/fd/0
Mit einem sehr wichtigen Unterschied. Wenn nicht "'\quote'"
die <<"'\LIMITER"'
eines here-document
dann wird die Schale für Schale bewerten $expansion
wie:
echo "[$CODE]" >/dev/fd/0
Also, für _gem_dec()
, die 3<<-FUNC here-document
als Datei auf Eingabe ausgewertet wird, gleich wie es wäre, wenn es waren 3<~/some.file
außer dass , weil wir das verlassen FUNC
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:
_gem_dec guard
Also muss die Shell zuerst die Eingabe verarbeiten, was bedeutet, dass sie liest:
3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
In Dateideskriptor 3 und Auswertung für die Shell-Erweiterung. Wenn Sie zu diesem Zeitpunkt gelaufen sind:
cat /dev/fd/3
Oder:
cat <&3
Da beide äquivalente Befehle sind, sehen Sie *:
_guard() { [ ! -e 'Gemfile' ] && {
command guard "$@" ; return $?
} || bundle exec guard "$@"
}
... 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 das here-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 als eval
.
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 $#
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 sei one two three
und die Gesamtstrom Positionsanzahl oder $#
3 sein würde , wenn ich rufe dann shift 2,
die Werte one
undtwo
würde shift
ed weg, den Wert $1
ändern würde three
und $#
würde erweitern , um 1. So shift $#
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:
( set -- one two three ; echo "$1 $2 $3" ; echo $# )
> one two three
> 3
( set -- one two three ; shift 2 ; echo "$1 $2 $3" ; echo $# )
> three
> 1
( set -- one two three ; shift $# ; echo "$1 $2 $3" ; echo $# )
>
> 0
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
die character device
spezielle Datei für unseren Dateideskriptor oder . /dev/fd/3
- wo unsere here-document
Inline-Datei gespeichert wurde - und unsere Funktion deklariert haben. Und so funktioniert es.
_guard
Jetzt macht alles, was Ihre _guard
Funktion tun soll.
Nachtrag:
Eine gute Möglichkeit zu sagen, speichern Sie Ihre Positionals:
f() { . /dev/fd/3
} 3<<-ARGS
args='${args:-"$@"}'
ARGS
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ösung 3<<-FUNC
die Form hatte:
3<<-FUNC
_${1}() {
if [ -e 'Gemfile' ]; then
bundle exec $1 "\$@"
else
command _${1} "\$@"
}
FUNC
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 nur guard
.
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:
Der Fragesteller möchte Shell-Funktionen mit dem Namen guard, rspec, or rake
, die beim Aufrufen eine ruby
Funktion mit demselben Namen neu kompilieren, in der if
die Datei Gemfile
existiert, $PATH
ODER
if Gemfile
nicht existiert. Die Shell-Funktion sollte die ruby
Funktion mit demselben Namen ausführen .
Das hätte vorher nicht geklappt, weil ich auch den $1
von command
gelesenen geändert habe:
command _${1}
Was nicht zur Ausführung der ruby
Funktion geführt hätte, die die Shell-Funktion kompiliert hat als:
bundle exec $1
Ich hoffe, Sie können sehen (wie ich es letztendlich getan habe), dass der Fragesteller anscheinend nur command
indirekt spezifiziert, namespace
weil er command
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:
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
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 die Gemfile's
Existenz überprüft und entsprechend kompiliert oder die guard
ausführbare Datei in ausgeführt wird $PATH
. Auf diese Weise namespace
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 von command
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üngliche if/then
Syntax. Auf diese Art und Weise die command
Anweisung wird nur dann ausgewertet , überhaupt , wenn Gemfile
nicht in ist $PATH
. Diese Änderung erfordert jedoch das Hinzufügen von, return $?
um sicherzustellen, dass die bundle
Anweisung nicht ausgeführt wird, falls das Ereignis Gemfile
nicht vorhanden ist, die ruby $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 Shell sh
oder ist dash
. Auch das oben Genannte shopts
wird in beiden und wie erwartet funktionieren (vorausgesetzt, es ist sowieso mindestens halbwegs gesund ) .bash
zsh
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.