Ich habe eine POSIX - Shell - Funktion geschrieben , die verwendet werden könnten , um lokal eine Shell builtin oder Funktion in einem Namespace ksh93
, dash
, mksh
, oder bash
(speziell genannt , weil ich persönlich es in all dieser Arbeit bestätigt habe) . Von den Shells, in denen ich es getestet habe, hat es nur meine Erwartungen nicht erfüllt yash
, und ich habe nie erwartet, dass es überhaupt funktioniert zsh
. Ich habe nicht getestet posh
. Ich habe vor posh
einiger Zeit jede Hoffnung aufgegeben und sie seit einiger Zeit nicht mehr installiert. Vielleicht funktioniert es in posh
...?
Ich sage, es ist POSIX, weil es beim Lesen der Spezifikation ein bestimmtes Verhalten eines grundlegenden Dienstprogramms ausnutzt, aber zugegebenermaßen ist die Spezifikation in dieser Hinsicht vage, und mindestens eine Person ist anscheinend anderer Meinung als ich. Im Allgemeinen hatte ich eine Meinungsverschiedenheit mit diesem, ich habe schließlich festgestellt, dass der Fehler mein eigener ist, und möglicherweise irre ich mich auch diesmal in Bezug auf die Spezifikation, aber als ich ihn weiter befragte, antwortete er nicht.
Wie ich bereits sagte, funktioniert dies definitiv in den oben genannten Schalen, und es funktioniert im Grunde auf folgende Weise:
some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"
3
empty
Der command
Befehl wird als grundsätzlich verfügbares Dienstprogramm und als eines der $PATH
vorgefertigten Elemente angegeben. Eine der angegebenen Funktionen besteht darin, spezielle integrierte Dienstprogramme beim Aufrufen in eine eigene Umgebung zu packen, und so ...
{ sh -c ' x=5 set --; echo "$x"
x=6 command set --; echo "$x"
exec <""; echo uh_oh'
sh -c ' command exec <""; echo still here'
}
5
5
sh: 3: cannot open : No such file
sh: 1: cannot open : No such file
still here
... das Verhalten der beiden oben genannten Befehlszeilenzuweisungen ist spezifikationsgemäß korrekt. Das Verhalten beider Fehlerzustände ist ebenfalls korrekt und wird dort tatsächlich fast vollständig aus der Spezifikation dupliziert. Zuweisungen, die den Befehlszeilen von Funktionen oder speziellen integrierten Funktionen vorangestellt werden, wirken sich auf die aktuelle Shell-Umgebung aus. Ebenso werden Umleitungsfehler als schwerwiegend angegeben, wenn auf einen dieser Fehler hingewiesen wird. command
wird spezifiziert, um die spezielle Behandlung von speziellen Builtins in diesen Fällen zu unterdrücken, und der Umleitungsfall wird tatsächlich anhand eines Beispiels in der Spezifikation demonstriert.
Regelmäßige Buildins command
werden beispielsweise so spezifiziert, dass sie in einer Subshell-Umgebung ausgeführt werden - was nicht unbedingt die eines anderen Prozesses bedeutet , sondern nur, dass er grundsätzlich nicht von einem zu unterscheiden ist. Die Ergebnisse des Aufrufs eines regulären Buildins sollten immer dem entsprechen, was mit einem ähnlich fähigen $PATH
Befehl erzielt werden kann . Und so...
na=not_applicable_to_read
na= read var1 na na var2 <<"" ; echo "$var1" "$na" "$var2"
word1 other words word2
word1 not_applicable_to_read word2
Der command
Befehl kann jedoch keine Shell-Funktionen aufrufen und kann daher nicht zum Rendern ihrer speziellen Behandlung verwendet werden, wie dies für reguläre integrierte Funktionen möglich ist. Das ist auch spezifiziert. Tatsächlich besagt die Spezifikation, dass ein primäres Dienstprogramm von darin command
besteht, dass Sie es innerhalb einer Wrapper-Shell-Funktion verwenden können, die nach einem anderen Befehl benannt ist, um diesen anderen Befehl ohne Selbstrekursion aufzurufen, da die Funktion nicht aufgerufen wird. So was:
cd(){ command cd -- "$1"; }
Wenn Sie es dort nicht verwenden würden, würde command
die cd
Funktion für die Selbstrekursion fast definitiv segfault.
Als reguläres Builtin, das spezielle Builtins aufrufen command
kann, kann dies jedoch in einer Subshell-Umgebung erfolgen . Und so, während aktuelle Shell-Staat im Macht - Stick auf den aktuell Shell definiert - sicher read
ist $var1
und $var2
hat - zumindest die Ergebnisse der Befehlszeile definiert wahrscheinlich soll nicht ...
Einfache Befehle
Wenn kein Befehlsname angezeigt wird oder wenn der Befehlsname eine spezielle integrierte Funktion ist, wirken sich Variablenzuweisungen auf die aktuelle Ausführungsumgebung aus. Andernfalls werden die Variablenzuweisungen für die Ausführungsumgebung des Befehls exportiert und haben keinen Einfluss auf die aktuelle Ausführungsumgebung.
Nun , ob oder nicht command
‚s Fähigkeit , sowohl eine regelmäßige builtin zu sein und direkt spezielle builtins zu nennen , ist nur eine Art unerwarteter Lücke in Bezug auf Befehlszeile definiert , weiß ich nicht, aber ich weiß , dass zumindest die vier Schalen bereits erwähnte Ehre den command
Namespace.
Und obwohl command
Shell-Funktionen nicht direkt aufgerufen werden können, kann eseval
wie gezeigt und indirekt aufgerufen werden . Also habe ich einen Namespace-Wrapper für dieses Konzept erstellt. Es braucht eine Liste von Argumenten wie:
ns any=assignments or otherwise=valid names which are not a command then all of its args
... außer dass das command
obige Wort nur dann als eins erkannt wird, wenn es mit einem Leerzeichen gefunden werden kann $PATH
. Neben lokal Scoping Shell - Variablen auf der Kommandozeile genannt, sondern auch lokal Tive alle Variable mit einzelnem Klein alphabetischem Namen und eine Liste von anderen Standard - one, wie $PS3
, $PS4
, $OPTARG
, $OPTIND
, $IFS
, $PATH
, $PWD
, $OLDPWD
und einig anderes.
Und ja, durch lokales Scoping der $PWD
und $OLDPWD
-Variablen und anschließendes explizites cd
Verweisen auf $OLDPWD
und $PWD
es kann auch das aktuelle Arbeitsverzeichnis ziemlich zuverlässig erfasst werden. Dies ist nicht garantiert, obwohl es ziemlich anstrengend ist. Es behält einen Deskriptor für 7<.
und wenn sein Wrap-Ziel zurückkehrt, tut es dies cd -P /dev/fd/7/
. Wenn das aktuelle Arbeitsverzeichnis unlink()
in der Zwischenzeit erstellt wurde, sollte es immer noch gelingen, es wieder zu ändern, aber in diesem Fall wird ein hässlicher Fehler ausgegeben. Und weil es den Deskriptor beibehält, denke ich nicht, dass ein vernünftiger Kernel zulassen sollte, dass sein Root-Gerät auch nicht gemountet wird (???) .
Außerdem werden Shell-Optionen lokal überprüft und in dem Zustand wiederhergestellt, in dem sie gefunden wurden, als das umschlossene Dienstprogramm zurückgegeben wurde. Es wird $OPTS
besonders dadurch behandelt, dass es eine Kopie in seinem eigenen Bereich verwaltet, der es anfänglich den Wert von zuweist $-
. Nachdem auch alle Zuweisungen in der Befehlszeile verarbeitet wurden, wird dies set -$OPTS
unmittelbar vor dem Aufrufen des Wrap-Ziels ausgeführt. Auf diese Weise können Sie, wenn Sie -$OPTS
in der Befehlszeile definieren, die Shell-Optionen Ihres Wrap-Ziels definieren. Wenn das Ziel zurückkehrt, wird es set +$- -$OPTS
mit einer eigenen Kopie von $OPTS
(die von den Befehlszeilendefinitionen nicht betroffen ist) und alle in den ursprünglichen Zustand zurückversetzen.
Natürlich hindert nichts den Aufrufer daran returrn
, die Funktion über das Wrap-Ziel oder seine Argumente explizit zu verlassen . Dies verhindert eine Wiederherstellung / Bereinigung des Zustands, die sonst versucht würde.
Um alles zu tun, muss es drei eval
tief gehen. Zuerst schließt es sich in einen lokalen Bereich ein, dann liest es von innen Argumente ein, validiert sie auf gültige Shell-Namen und beendet sie mit einem Fehler, wenn es einen findet, der nicht vorhanden ist. Wenn alle Argumente gültig sind und schließlich command -v "$1"
true zurückgegeben wird (Rückruf: $PATH
ist an dieser Stelle leer) , werden in eval
der Befehlszeile alle verbleibenden Argumente definiert und an das Wrap-Ziel übergeben (obwohl der Sonderfall für ns
- ignoriert wird, da dies nicht der Fall wäre) nicht sehr nützlich sein, und drei eval
s tief ist mehr als tief genug) .
Es funktioniert im Grunde so:
case $- in (*c*) ... # because set -c doesnt work
esac
_PATH=$PATH PATH= OPTS=$- some=vars \
command eval LOCALS=${list_of_LOCALS}'
for a do i=$((i+1)) # arg ref
if [ "$a" != ns ] && # ns ns would be silly
command -v "$a" &&
! alias "$a" # aliases are hard to run quoted
then eval " PATH=\$_PATH OTHERS=$DEFAULTS $v \
command eval '\''
shift $((i-1)) # leave only tgt in @
case $OPTS in (*different*)
set \"-\${OPTS}\" # init shell opts
esac
\"\$@\" # run simple command
set +$- -$OPTS "$?" # save return, restore opts
'\''"
cd -P /dev/fd/7/ # go whence we came
return "$(($??$?:$1))" # return >0 for cd else $1
else case $a in (*badname*) : get mad;;
# rest of arg sa${v}es
esac
fi; done
' 7<.
Es gibt einige andere Umleitungen und, und ein paar seltsame Tests mit der Art und Weise zu tun , einige Shells setzen c
in $-
ablehnen und dann zu akzeptieren , als Option set
(???) , aber es ist alles untergeordnet, und in erster Linie verwendet , nur von speichern emittierenden unerwünschte Ausgabe und ähnliches in Randfällen. Und so funktioniert es. Es kann diese Dinge tun, weil es seinen eigenen lokalen Bereich einrichtet, bevor es sein umschlossenes Dienstprogramm in einem verschachtelten solchen aufruft.
Es ist lang, weil ich hier sehr vorsichtig sein will - drei evals
sind schwer. Aber damit können Sie tun:
ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"
still_local global
global
Es sollte nicht sehr schwierig sein, einen Schritt weiter zu gehen und den lokalen Bereich des umschlossenen Dienstprogramms dauerhaft zu benennen. Und selbst wie geschrieben definiert es bereits eine $LOCALS
Variable für das umschlossene Dienstprogramm, die nur aus einer durch Leerzeichen getrennten Liste aller Namen besteht, die es in der Umgebung des umschlossenen Dienstprogramms definiert hat.
Mögen:
ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '
... was absolut sicher ist - $IFS
wurde auf seinen Standardwert bereinigt und nur gültige Shell-Namen schaffen es, es $LOCALS
sei denn, Sie setzen es selbst in der Befehlszeile. Und selbst wenn eine geteilte Variable möglicherweise Glob-Zeichen enthält, können Sie in OPTS=f
der Befehlszeile festlegen, dass das umschlossene Dienstprogramm deren Erweiterung verhindert. Auf jeden Fall:
LOCALS ARG0 ARGC HOME
IFS OLDPWD OPTARG OPTIND
OPTS PATH PS3 PS4
PWD a b c
d e f g
h i j k
l m n o
p q r s
t u v w
x y z _
bel bs cr esc
ht ff lf vt
lb dq ds rb
sq var1 var2
Und hier ist die Funktion. Allen Befehlen wird ein Präfix vorangestellt \
, um alias
Erweiterungen zu vermeiden :
ns(){ ${1+":"} return
case $- in
(c|"") ! set "OPTS=" "$@"
;; (*c*) ! set "OPTS=${-%c*}${-#*c}" "$@"
;; (*) set "OPTS=$-" "$@"
;; esac
OPTS=${1#*=} _PATH=$PATH PATH= LOCALS= lf='
' rb=\} sq=\' l= a= i=0 v= __=$_ IFS=" ""
" command eval LOCALS=\"LOCALS \
ARG0 ARGC HOME IFS OLDPWD OPTARG OPTIND OPTS \
PATH PS3 PS4 PWD a b c d e f g h i j k l m n \
o p q r s t u v w x y z _ bel bs cr esc ht ff \
lf vt lb dq ds rb sq'"
for a do i=$((i+1))
if \[ ns != "$a" ] &&
\command -v "$a" >&9 &&
! \alias "${a%%=*}" >&9 2>&9
then \eval 7>&- '\' \
'ARGC=$((-i+$#)) ARG0=$a HOME=~' \
'OLDPWD=$OLDPWD PATH=$_PATH IFS=$IFS' \
'OPTARG=$OPTARG PWD=$PWD OPTIND=1' \
'PS3=$PS3 _=$__ PS4=$PS4 LOCALS=$LOCALS' \
'a= b= c= d= e= f= g= i=0 j= k= l= m= n= o=' \
'p= q= r= s= t= u= v= w= x=0 y= z= ht=\ ' \
'cr=^M bs=^H ff=^L vt=^K esc=^[ bel=^G lf=$lf' \
'dq=\" sq=$sq ds=$ lb=\{ rb=\}' \''"$v' \
'\command eval 9>&2 2>&- '\' \
'\shift $((i-1));' \
'case \${OPTS##*[!A-Za-z]*} in' \
'(*[!c$OPTS]*) >&- 2>&9"'\' \
'\set -"${OPTS%c*}${OPTS#*c}"' \
';;esac; "$@" 2>&9 9>&-; PS4= ' \
'\set +"${-%c*}${-#*c}"'\'\" \
-'$OPTS \"\$?\"$sq";' \
' \cd -- "${OLDPWD:-$PWD}"
\cd -P ${ANDROID_SYSTEM+"/proc/self/fd/7"} /dev/fd/7/
\return "$(($??$?:$1))"
else case ${a%%=*} in
([0-9]*|""|*[!_[:alnum:]]*)
\printf "%s: \${$i}: Invalid name: %s\n" \
>&2 "$0: ns()" "'\''${a%%=*}'\''"
\return 2
;; ("$a") v="$v $a=\$$a"
;; (*) v="$v ${a%%=*}=\${$i#*=}"
;; esac
case " $LOCALS " in (*" ${a%%=*} "*)
;; (*) LOCALS=$LOCALS" ${a%%=*}"
;; esac
fi
done' 7<. 9<>/dev/null
}
( easiest thing ever )
. Aber das ist nicht ganz das, wonach du suchst. Ich denke, Sie könnten es tun( stuff in subshell; exec env ) | sed 's/^/namespace_/'
undeval
das Ergebnis in der übergeordneten Shell, aber das ist irgendwie böse.