Shell-Namespaces

10

Gibt es eine Möglichkeit, sourceein Shell-Skript in einen Namespace zu verschieben, vorzugsweise ein Bash-Shell-Skript, aber ich würde andere Shells untersuchen, wenn sie diese Funktion hätten und Bash nicht.

Was ich damit meine, ist z. B. "Präfixieren aller definierten Symbole mit etwas, damit sie nicht mit bereits definierten Symbolen (Variablennamen, Funktionsnamen, Aliasnamen) kollidieren" oder eine andere Funktion, die Namenskollisionen verhindert.

Wenn es eine Lösung gibt, bei der ich gleichzeitig einen Namespace source( NodeJSStil) erstellen kann, ist dies die beste.

Beispielcode:

$ echo 'hi(){ echo Hello, world; }' > english.sh
$ echo 'hi(){ echo Ahoj, světe; }' > czech.sh
$ . english.sh
$ hi
 #=> Hello, world
$ . czech.sh #bash doesn't even warn me that `hi` is being overwritten here
$ hi
 #=> Ahoj, světe
#Can't use the English hi now
#And sourcing the appropriate file before each invocation wouldn't be very efficient 
PSkocik
quelle
1
Danke für die Klarstellung. Ich erwarte, dass die Antwort negativ ist. Das übliche Shell-Programmierparadigma ist, dass Sie Änderungen in einer Subshell ausführen möchten, wenn Sie Änderungen isolieren möchten ( 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_/'und evaldas Ergebnis in der übergeordneten Shell, aber das ist irgendwie böse.
Celada
3
Ja. Holen Sie sich ksh93. Namespaces sind von grundlegender Bedeutung - und alle Namenstypen (die auch typisierbar sind) unterstützen den Namespace. Es ist auch in praktisch jeder Hinsicht viel schneller als bashübrigens.
Mikeserv
@mikeserv Danke, wenn Sie es als Antwort mit einem Codebeispiel hinzufügen, das die Funktionalität demonstriert, werde ich es akzeptieren.
PSkocik
@michas Ich müsste auch Funktionssymbole und Aliase benennen. env | sed ...würde für Variablen setfunktionieren , ich könnte tun , um Funktionen zu erhalten, aber das Suchen und Ersetzen wäre ein Problem - Funktionen können sich gegenseitig aufrufen und Sie, so dass Sie alle Kreuzaufrufe durch vorangestellte Kreuzaufrufe ersetzen müssten, ohne jedoch die zu ersetzen gleiche Wörter an anderer Stelle im Funktionsdefinitionscode, wo es sich nicht um einen Aufruf handelt. Dafür benötigen Sie einen Bash-Parser, nicht nur einen regulären Ausdruck, und er funktioniert immer noch nur, solange sich die Funktionen nicht über eval gegenseitig aufrufen.
PSkocik

Antworten:

11

Von man kshauf einem System mit einem ksh93installierten ...

  • Namensräume
    • Befehle und Funktionen, die als Teil der Liste eines namespaceBefehls ausgeführt werden, der Variablen ändert oder neue erstellt, erstellen eine neue Variable, deren Name der Name des Namensraums ist, der durch den vorangestellten Bezeichner angegeben wird .. Wenn auf eine Variable verwiesen wird, deren Name Name ist, wird zuerst nach deren Verwendung gesucht .identifier.name.
    • In ähnlicher Weise wird eine durch einen Befehl in der Namespace-Liste definierte Funktion unter Verwendung des Namensraumnamens mit vorangestelltem a erstellt ..
    • Wenn die Liste eines Namespace-Befehls einen Befehl enthält namespace, bestehen die Namen der erstellten Variablen und Funktionen aus dem Variablen- oder Funktionsnamen, dem die Liste der Bezeichner vorangestellt ist .. Außerhalb eines Namensraums kann auf eine Variable oder Funktion verwiesen werden, die in einem Namensraum erstellt wurde, indem der Name des Namensraums vorangestellt wird.
    • Standardmäßig befinden sich Variablen, mit .shdenen gestarrt wird, im shNamensraum.

Zur Veranschaulichung wird hier das Konzept auf einen Namespace angewendet, der standardmäßig für jede reguläre Shell-Variable bereitgestellt wird, die in einer ksh93Shell zugewiesen ist . Im folgenden Beispiel werde ich eine disciplineFunktion definieren , die als zugewiesene .getMethode für die $PS1Shell-Variable fungiert. Jedes Shell - Variable wird im Grunde seinen eigenen Namensraum mit zumindest der Standard get, set, append, und unsetMethoden. Nach dem Definieren der folgenden Funktion wird $PS1die Ausgabe von jedes Mal, wenn auf die Variable in der Shell verwiesen datewird, am oberen Bildschirmrand gezeichnet ...

function PS1.get {
    printf "\0337\33[H\33[K%s\0338" "${ date; }"
}

(Beachten Sie auch das Fehlen der ()Unterschale in der obigen Befehlsersetzung.)

Technisch gesehen sind Namespaces und Disziplinen nicht genau dasselbe (da Disziplinen so definiert werden können, dass sie entweder global oder lokal auf einen bestimmten Namespace angewendet werden ) , aber sie sind sowohl Bestandteil der Konzeptualisierung von Shell-Datentypen, die für sie von grundlegender Bedeutung ist ksh93.

Um Ihre speziellen Beispiele anzusprechen:

echo 'function hi { echo Ahoj, světe\!;  }' >  czech.ksh
echo 'function hi { echo Hello, World\!; }' >english.ksh
namespace english { . ./english.ksh; }
namespace czech   { . ./czech.ksh;   }
.english.hi; .czech.hi

Hello, World!
Ahoj, světe!

...oder...

for ns in czech english
do  ".$ns.hi"
done

Ahoj, světe!
Hello, World!
mikeserv
quelle
@PSkocik - warum hast du mein ehoj Ding nicht repariert ? Ich hätte schwören können, dass es vorher gesagt wurde ... Entschuldigung. Ich hätte eine Antwort von jemandem nicht akzeptiert, der sich nicht einmal die Mühe gemacht hat, die Wörter, die ich in der Frage verwendet habe, richtig zu buchstabieren ... Ehrlich gesagt denke ich, ich erinnere mich, dass ich nur jt kopiert / eingefügt habe ... hmm ...
Mikeserv
2

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 posheiniger 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 commandBefehl wird als grundsätzlich verfügbares Dienstprogramm und als eines der $PATHvorgefertigten 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. commandwird 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 commandwerden 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 $PATHBefehl 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 commandBefehl 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 commandbesteht, 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 commanddie cdFunktion für die Selbstrekursion fast definitiv segfault.

Als reguläres Builtin, das spezielle Builtins aufrufen commandkann, kann dies jedoch in einer Subshell-Umgebung erfolgen . Und so, während aktuelle Shell-Staat im Macht - Stick auf den aktuell Shell definiert - sicher readist $var1und $var2hat - 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 commandNamespace.

Und obwohl commandShell-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 commandobige 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, $OLDPWDund einig anderes.

Und ja, durch lokales Scoping der $PWDund $OLDPWD-Variablen und anschließendes explizites cdVerweisen auf $OLDPWDund $PWDes 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 $OPTSbesonders 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 -$OPTSunmittelbar vor dem Aufrufen des Wrap-Ziels ausgeführt. Auf diese Weise können Sie, wenn Sie -$OPTSin der Befehlszeile definieren, die Shell-Optionen Ihres Wrap-Ziels definieren. Wenn das Ziel zurückkehrt, wird es set +$- -$OPTSmit 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 evaltief 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: $PATHist an dieser Stelle leer) , werden in evalder 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 evals 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 cin $-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 evalssind 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 $LOCALSVariable 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 - $IFSwurde auf seinen Standardwert bereinigt und nur gültige Shell-Namen schaffen es, es $LOCALSsei 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=fder 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 aliasErweiterungen 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
}
mikeserv
quelle
Sehr schlau! Ein ähnliches Muster wird verwendet, um hier fast
Zac B