Google Gruppi non supporta più i nuovi post o le nuove iscrizioni Usenet. I contenuti storici continuano a essere visibili.

Wieso ist bash variable in subshell vorhanden?

0 visualizzazioni
Passa al primo messaggio da leggere

Ulli Horlacher

da leggere,
23 ago 2022, 21:24:3423/08/22
a

framstag@juhu:~: echo $BASH_VERSION
4.4.20(1)-release

framstag@juhu:~: set -o|grep export
allexport off

framstag@juhu:~: unset x; x=X;(echo $x)
X

Wieso ist x in der subshell (neuer Prozess!) vorhanden?
Ich haette erwartet, dass es dazu ein export bedarf?

Es ist zumindest keine environment Variable:

framstag@juhu:~: unset x; x=X;(env | grep ^x=)
framstag@juhu:~: unset x; export x=X;(env | grep ^x=)
x=X

--
Ullrich Horlacher Server und Virtualisierung
Rechenzentrum TIK
Universitaet Stuttgart E-Mail: horl...@tik.uni-stuttgart.de
Allmandring 30a Tel: ++49-711-68565868
70569 Stuttgart (Germany) WWW: http://www.tik.uni-stuttgart.de/

Ralf Damaschke

da leggere,
23 ago 2022, 22:02:0223/08/22
a
Ulli Horlacher schrieb:

> framstag@juhu:~: echo $BASH_VERSION
> 4.4.20(1)-release
>
> framstag@juhu:~: set -o|grep export
> allexport off
>
> framstag@juhu:~: unset x; x=X;(echo $x)
> X
>
> Wieso ist x in der subshell (neuer Prozess!) vorhanden?
> Ich haette erwartet, dass es dazu ein export bedarf?

Zunächst übergibst Du ja selbst den Wert X an den subshell.
Doch auch der Aufruf "(eval 'echo $x')" würde X ausgeben,
nicht aber "(sh -c 'echo $x')".

Die Kommandoliste in den runden Klammern wird in einer subshell
environment ausgeführt, nicht in einer separaten shell execution
environment.

<https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_12>,
insbesondere letzter Absatz.

Enrik Berkhan

da leggere,
24 ago 2022, 02:13:0524/08/22
a
Auch das kann man, wie neulich hier schon diskutiert, mit 'sh -x' ganz
gut testen oder sehen:

#v+
$ sh -xc "unset x; x=X; (echo \$x)"
+ unset x
+ x=X
+ echo X
X
$ sh -xc "unset x; x=X; (eval 'echo \$x')"
+ unset x
+ x=X
+ eval echo $x
+ echo X
X
$ sh -xc "unset x; x=X; (sh -c 'echo \$x')"
+ unset x
+ x=X
+ sh -c echo $x

$
#v-

Helmut Waitzmann

da leggere,
24 ago 2022, 07:52:3924/08/22
a
Ulli Horlacher <fram...@rus.uni-stuttgart.de>:
> framstag@juhu:~: echo $BASH_VERSION
> 4.4.20(1)-release
>
> framstag@juhu:~: set -o|grep export
> allexport off
>
> framstag@juhu:~: unset x; x=X;(echo $x)
> X
>
> Wieso ist x in der subshell (neuer Prozess!) vorhanden?
> Ich haette erwartet, dass es dazu ein export bedarf?

Die traditionelle Implementierung des Subshell‐Environments («(…)»)
wird mit dem Systemaufruf «fork» gemacht (zu diesem Systemaufruf
siehe die Handbuchseite «fork(2)», das heißt, der Shell erzeugt
eine Prozesskopie, also einen Doppelgänger, von sich selbst.  Eine
Prozesskopie zu sein, bedeutet, dass der neu erzeugte Prozess eine
(nahezu[1] gleiche) Kopie des Virtuellspeicherinhalts des
erzeugenden Prozesses (den gleichen Programmcode, die gleichen
Daten, die gleiche File‐Descriptor‐Tabelle und weiteres) erhält. 
Dadurch erhält er den kompletten Prozesszustand des erzeugenden
Prozesses.

[1] Die Kopie ist nicht exakt gleich:  Beispielsweise erhält der
neue Prozess eine eigene Prozessnummer, die sich von der
unterscheidet, die der alte hat.

Im Falle des Shells gehören auch die Shell‐Variablen zum
Prozesszustand des Prozesses.

Wenn man mit einem Prozessdebugger beide Prozesse untersuchen
würde, würde man feststellen, dass man in beiden Hinweise darauf
findet, dass vor kurzem der Systemaufruf „fork“ aufgerufen und
beendet worden ist – und das, obwohl nur einer von beiden, nämlich
der Erzeugerprozess, „fork“ aufgerufen hat.  Der neu erzeugte
Prozess kommt gewissermaßen mit einer fiktiven Vergangenheit zur
Welt, die er zwar nicht selbst «erlebt» hat, an die er sich aber
«erinnert», als hätte er sie selbst erlebt.  Der neueste Teil
dieser Erinnerung an eine fiktive Vergangenheit ist die Rückkehr
aus dem Systemaufruf «fork», denn ab da endet die fiktive
Vergangenheit und der neue Prozess lebt wirklich selber.

Ein kurzes Beispielprogramm, an dem man sieht, wie beide Prozesse
trotzdem anschließend unterschiedliche Wege gehen, obwohl sie
denselben Programmcode und die gleichen Daten im
Usermode‐Virtuellspeicher haben, gibt es auf der Handbuchseite
«wait(2)» zum Systemaufruf „wait“.  Es sei im Voraus verraten:  Es
hat mit dem Funktionsergebnis des Systemaufrufs «fork» zu tun.

Der Shell‐Prozess, der „fork“ aufgerufen hat, arbeitet weiterhin
seinen Shell‐Programmcode ab:  Er tut, wenn das zu startende
Subshell‐Environment im Vordergrund laufen soll, im Wesentlichen
nichts anderes, als auf das Ende des neu erzeugten Prozesses mit
einem der Systemaufrufe „wait“, „waitpid“ oder „waitid“ zu warten.

Der neu erzeugte Shell‐Prozess (das Subshell‐Environment) arbeitet
die Shellkommandos innerhalb der runden Klammern ab.

Erst dann, wenn der neu erzeugte Prozess den Systemaufruf «execve»
aufruft, um ein anderes Programm (in deinem Beispiel: «echo»)
laufen zu lassen, verliert er seinen kompletten
Usermode‐Virtuellspeicherinhalt, einschließlich der
Umgebungsvariablen (denn die sind auch nur Teil des
Usermode‐Virtuellspeicherinhalts).  Umgebungsvariable kommen über
die «execve»‐Schranke zum neuen Prozess nur dadurch, dass sie beim
«execve»‐Systemaufruf ähnlich wie die Aufrufparameter in einer
Liste der Umgebungsvariablen übergeben werden.  Das ist in der
Handbuchseite «execve(2)» der dritte Parameter beim
«execve»‐Aufruf:

int execve(const char *filename, char *const argv[],
char *const envp[]);

Im «fork/execve»‐Beispiel in der Handbuchseite «execve(2)» sieht
das so aus (Ausschnitt):

char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);

Der Beispielersteller hat das Beispiel auf ein absolutes Minimum
gebracht:  Als dritten Parameter des Systemaufrufs «execve», der
Liste der Umgebungsvariablen, gibt er eine leere Liste an.  Dadurch
hat der Prozess nach dem Aufruf von «execve» keine
Umgebungsvariablen mehr.  In der Praxis gibt man als Liste der
Umgebungsvariablen die Variable «environ» (siehe die Handbuchseite
«environ(7)») an.  Sie enthält die Umgebungsvariablen des «execve»
aufrufenden Prozesses.

Der Shell gibt dort seine Liste der Shell‐Variablen, die «für den
Export in die Umgebung markiert» sind, an.  In der Beschreibung des
Effekts des Shell‐Kommandos «export» in der Handbuchseite «bash(1)»
heißt es:

export -p
The supplied names are marked for automatic export to
the environment of subsequently executed commands.

Ich bin mir sicher, unzählige Leser – mich eingeschlossen – haben
sich beim Lesen über diese seltsam umständliche Formulierung
gewundert – ich auch, bis ich den Bach (siehe unten) gelesen hatte.


Schau dir dringend die Beispiele an, oder, viel besser noch, lies
ein Buch über die Unix‐Interna.  Und dir als Systemadministrator
sage ich:  Tue es auf der Stelle!  Lege jedes Buch «So funktioniert
die Shell» oder «Shell‐Programmierung für Systemadministratoren»
zur Seite.  Die werden dir alle nicht helfen, ebensowenig wie die
Handbuchseite «bash(1)»:  Die Manual-Pages der Shells erwarten vom
Leser, dass er bereits in groben Zügen weiß, wie Unix oder Linux
funktioniert.


Mir hat das Buch


Maurice J. Bach: The Design Of The Unix Operating System

Prentice-Hall International, London, 1986


sehr zum Verständnis geholfen.  Es soll auch eine deutsche
Übersetzung (»So funktioniert das UNIX Betriebssystem« oder so
ähnlich) geben.  Obwohl inzwischen Jahrzehnte alt, liefert es
u. a. genau das Wissen, das dir fehlt.  »Still a good read«, meinte
mal eine Rezension.

Es enthält viele Codeschnipsel in C-ähnlichem Pseudeocode, die die
Vorgehensweise des Betriebssystemkerns verdeutlichen, und
Übungsaufgaben, die dem Leser helfen, die richtigen Fragen zu
stellen.

Sicher gibt es inzwischen neuere Bücher.  (Ich kenne aber keins.)


Ich prophezeie dir:  Wenn du ein entsprechendes Buch gelesen hast,
kannst du dich beim Lesen des Shell-Handbuchs in Abwandlung eines
Spruchs von Beate Goebel vor Aha-Erlebnissen nicht mehr retten: 
Hat man erst mal verstanden, wie Unix funktioniert, versteht man
auch das Shell-Handbuch.

--
Hat man erst verstanden, wie Unix funktioniert, ist auch
das Shell-Handbuch kein Buch mit sieben Siegeln mehr.

Helmut Waitzmann

da leggere,
24 ago 2022, 07:52:4124/08/22
a
Ralf Damaschke <rws...@gmx.de>:
> Ulli Horlacher schrieb:
>
>> framstag@juhu:~: echo $BASH_VERSION
>> 4.4.20(1)-release
>>
>> framstag@juhu:~: set -o|grep export
>> allexport off
>>
>> framstag@juhu:~: unset x; x=X;(echo $x)
>> X
>>
>> Wieso ist x in der subshell (neuer Prozess!) vorhanden?
>> Ich haette erwartet, dass es dazu ein export bedarf?
>
> Zunächst übergibst Du ja selbst den Wert X an den subshell.
>

Nein.  An den Sub‐Shell wird tatsächlich die Zeichenfolge


echo $x

übergeben.  Vom Variablewert «X» sieht er in der Kommandozeile, so,
wie er sie erhält, nichts.  Die Variable «x» beim «echo»‐Kommando
wertet er selber aus.  Ullis Beispiel ist daher durchaus geeignet,
seine Beobachtung und Verwunderung zu untermauern.

> Doch auch der Aufruf "(eval 'echo $x')" würde X ausgeben,
>

Ja, denn das ist effektiv derselbe Fall:  Die Kommandozeilen


echo $x

und

eval 'echo $x'

laufen auf exakt dasselbe hinaus: auf eine Kommandozeile mit dem
Inhalt

echo $x


> nicht aber "(sh -c 'echo $x')".
>
> Die Kommandoliste in den runden Klammern wird in einer subshell
> environment ausgeführt, nicht in einer separaten shell execution
> environment.

Genau.  Wie nebenan beschrieben, kommt beim Programmaufruf «sh» der
Systemaufruf «execve» (oder eine Simulation davon) ins Spiel, der
den Usermode‐Teil des Virtuellspeicherinhalts – und mit ihm auch die
Shell‐Variable «x» wegwirft.

Ulli Horlacher

da leggere,
24 ago 2022, 17:19:0424/08/22
a
Helmut Waitzmann <nn.th...@xoxy.net> wrote:

> > framstag@juhu:~: unset x; x=X;(echo $x)
> > X
> >
> > Wieso ist x in der subshell (neuer Prozess!) vorhanden?
> > Ich haette erwartet, dass es dazu ein export bedarf?
>
> Die traditionelle Implementierung des Subshell?Environments («(?)»)
> wird mit dem Systemaufruf «fork» gemacht

AHHHH! Das erklaert das Verhalten!
Wo kann man das nachlesen (also dass subshells durch fork erzeugt werden)?
In der man-page habe ich nichts dazu gefunden, allerdings ist die auch
seeeeehr lang und ich hab nicht alles gelesen.


> Im Falle des Shells gehören auch die Shell?Variablen zum
> Prozesszustand des Prozesses.

Klar. Jetzt :-)


> Der neu erzeugte Shell?Prozess (das Subshell?Environment) arbeitet
> die Shellkommandos innerhalb der runden Klammern ab.
>
> Erst dann, wenn der neu erzeugte Prozess den Systemaufruf «execve»
> aufruft, um ein anderes Programm (in deinem Beispiel: «echo»)

Knapp daneben :-)

framstag@juhu:~: type echo
echo is a shell builtin


> Der Shell gibt dort seine Liste der Shell?Variablen, die «für den
> Export in die Umgebung markiert» sind, an. 

Auch klar. Ich hatte in meinem Beispiel extra eine normale Shell Variable
verwendet.

Ralf Damaschke

da leggere,
24 ago 2022, 19:40:2624/08/22
a
Helmut Waitzmann schrieb:
> Ralf Damaschke <rws...@gmx.de>:
>> Ulli Horlacher schrieb:
>>
>>> framstag@juhu:~: unset x; x=X;(echo $x)
>>> X
>>>

>> Zunächst übergibst Du ja selbst den Wert X an den subshell.
>>
>
> Nein.  An den Sub‐Shell wird tatsächlich die Zeichenfolge
>
> echo $x
>
> übergeben.  Vom Variablewert «X» sieht er in der Kommandozeile, so,
> wie er sie erhält, nichts.  Die Variable «x» beim «echo»‐Kommando
> wertet er selber aus.  Ullis Beispiel ist daher durchaus geeignet,
> seine Beobachtung und Verwunderung zu untermauern.

Oh ja, stimmt. Das war mir nicht klar. Es hat auch eine Weile gedauert,
bis ich dazu den relevanten Satz im Abschnitt "Word expansions" fand.

Dass die Parameter-Auswertung erst im subshell stattfindet, lässt sich
mit "x=; (echo 1 ${x:=ABC}); echo 2 $x" veranschaulichen.

Helmut Waitzmann

da leggere,
25 ago 2022, 18:14:4025/08/22
a
Ulli Horlacher <fram...@rus.uni-stuttgart.de>:
>Helmut Waitzmann <nn.th...@xoxy.net> wrote:
>
>>> framstag@juhu:~: unset x; x=X;(echo $x)
>>> X
>>>
>>> Wieso ist x in der subshell (neuer Prozess!) vorhanden?
>>>
>>> Ich haette erwartet, dass es dazu ein export bedarf?
>>>
>>
>> Die traditionelle Implementierung des Subshell?Environments
>> («(?)») wird mit dem Systemaufruf «fork» gemacht
>
>AHHHH! Das erklaert das Verhalten!
>
>Wo kann man das nachlesen (also dass subshells durch fork erzeugt
>werden)? In der man-page habe ich nichts dazu gefunden, allerdings
>ist die auch seeeeehr lang und ich hab nicht alles gelesen.

In «bash(1)» finde ich nur im Abschnitt «SHELL GRAMMAR»:


(list) list is executed in a subshell environment
(see COMMAND EXECUTION ENVIRONMENT below).
Variable assignments and builtin commands
that affect the shell's environment do not
remain in effect after the command
completes. The return status is the exit
status of list.


Also, ich glaube, im POSIX‐Standard steht es auch nicht
ausdrücklich.  Der erlaubt es dem Shell, das auch anders
hinzubekommen.  Die Wirkung muss aber entsprechend sein.  Wenn ich
mir aber vorstelle, dass beispielsweise auch Signale, die in

(list)

einschlagen könnten – insbesondere solche, die nicht abgefangen,
ignoriert oder blockiert werden können, wie KILL oder STOP –, oder
auch die Wahl des Arbeitsverzeichnisses in Verbindung mit core
dumps, nach außen hin keine Wirkung haben dürfen, sehe ich
eigentlich keine andere Möglichkeit, als einen eigenen Prozess,
mithin «fork» zu verwenden.

>> Im Falle des Shells gehören auch die Shell?Variablen zum
>> Prozesszustand des Prozesses.
>
>Klar. Jetzt :-)
>
>
>> Der neu erzeugte Shell?Prozess (das Subshell?Environment) arbeitet
>> die Shellkommandos innerhalb der runden Klammern ab.
>>
>> Erst dann, wenn der neu erzeugte Prozess den Systemaufruf «execve»
>> aufruft, um ein anderes Programm (in deinem Beispiel: «echo»)
>
>Knapp daneben :-)
>
>
>framstag@juhu:~: type echo
>echo is a shell builtin
>

Ja, daran habe ich beim Schreiben auch gedacht, dass «echo»
heutzutage meistens eingebaut ist.  Aber der Shell ist in dem Fall
verpflichtet, das externe «echo» zu simulieren.  Außerdem ging es
mir dabei mehr darum, zu beschreiben, was bei «execve» geschieht, um
klarzustellen, wann denn nun nicht‐exportierte Shell‐Variable nicht
an neue Shell‐Prozesse weitergereicht werden. 

Beispielsweise ist es ja das erwartete Verhalten, dass in der
Kommandozeile

(
unset -v -- variable && variable=Wert &&
sh -c -- 'echo "$variable' sh
)

nicht zu erwarten ist, dass man die Ausgabe «Wert» erhält.  Mit
«fork» allein ist das nicht erklärt.  Das ist deshalb der Fall, weil
«execve» dem neu erzeugten Shell‐Prozess nichts mehr vom alten
Speicherinhalt lässt, mithin also auch die Shell‐Variable «variable»
nicht mehr da ist.  Denn wie zuvor beschrieben, räumt «fork» den
Speicherinhalt nicht auf sondern kopiert ihn vollständig.

Helmut Waitzmann

da leggere,
25 ago 2022, 18:14:4225/08/22
a
Ralf Damaschke <rws...@gmx.de>:
Genau.

Dass Variablen bereits vorher ersetzt werden, das gibt es im Shell
auch: mit «eval», wenn die Variablenauswertung nicht durch single
quotes oder backslashes verhindert wird:

(
unset -v -- v1 v2
v1=v2 &&
eval 'unset -v -- v1 && '"$v1"'=v3' &&
printf '%s\n' "$v2"
)

Ralf Damaschke

da leggere,
25 ago 2022, 20:04:1325/08/22
a
Helmut Waitzmann schrieb:

> Dass Variablen bereits vorher ersetzt werden, das gibt es im Shell
> auch: mit «eval», wenn die Variablenauswertung nicht durch single
> quotes oder backslashes verhindert wird:
>
> (
> unset -v -- v1 v2
> v1=v2 &&
> eval 'unset -v -- v1 && '"$v1"'=v3' &&
> printf '%s\n' "$v2"
> )

Hmm. Du willst vielleicht sagen, dass mit eval gezeigt werden kann,
dass vor Ausführung eines simple commands (hier: eval ...) die
word expansions stattfinden (hier: "$v1").

Helmut Waitzmann

da leggere,
26 ago 2022, 14:23:0026/08/22
a
Ralf Damaschke <rws...@gmx.de>:
Ich will damit sagen, dass das, was du bei den runden Klammern
vermutet hast, (zwar nicht bei den runden Klammern, aber) in
Verbindung mit «eval» möglich ist:  Variablenexpansionen in einer
Zeichenkette, die eine Kommandozeile werden soll, werden
ausgewertet, ehe diese Zeichenkette als Kommandozeile ausgewertet
wird.  Der Grund ist, dass man mit «eval» eine zweimalige
Auswertung erhält.  (Deshalb ist «eval» nur mit größter Vorsorge zu
genießen.)

Dass vor Ausführung eines simple commands die word expansions
stattfinden, das ist bei jedem simple command – natürlich auch bei
«eval» – der Fall und nichts besonderes und doch sicher hier
allgemein anerkannt.

Ralf Damaschke

da leggere,
26 ago 2022, 19:07:1426/08/22
a
Helmut Waitzmann schrieb:
Eben. Und die Auswertung in
command 'param-start'"$variable"'param-end'
ist doch nichts anderes.

Helmut Waitzmann

da leggere,
27 ago 2022, 13:57:1227/08/22
a
Ralf Damaschke <rws...@gmx.de>:
> Helmut Waitzmann schrieb:
>> Dass vor Ausführung eines simple commands die word expansions
>> stattfinden, das ist bei jedem simple command – natürlich auch
>> bei «eval» – der Fall und nichts besonderes und doch sicher
>> hier allgemein anerkannt.
>
> Eben. Und die Auswertung in
> command 'param-start'"$variable"'param-end'
> ist doch nichts anderes.
>

Zurück auf Los:  Ulli wunderte sich, warum die Kommandozeile


unset x; x=X;(echo $x)

ein «X» ausgibt.  Du kommentierst darauf mit «Zunächst übergibst
Du ja selbst den Wert X an den subshell.»

In Ullis Kommandozeile ist aber, was den Subshell angeht,
nirgendwo das «X» zu sehen.  Zu sehen ist die Variable «x», die
den Wert «X» enthält.

Da du aber ausdrücklich geschrieben hast «übergibst den Wert X an
den Subshell» und nicht etwa «übergibst die Variable x mit ihrem
Wert X an den Subshell», muss ich daraus schließen, dass du damit
sagen wolltest, dass die Variable «x» im Kommandzeilenteil

(echo $x)

bereits vor dem Start des Subshells expandiert ist und der
Subshell also in Wirklichkeit

(echo X)

zu sehen bekommt.  Verstehe ich dich damit richtig?

Ralf Damaschke

da leggere,
27 ago 2022, 19:41:1427/08/22
a
Helmut Waitzmann schrieb:

> Da du aber ausdrücklich geschrieben hast «übergibst den Wert X an
> den Subshell» und nicht etwa «übergibst die Variable x mit ihrem
> Wert X an den Subshell», muss ich daraus schließen, dass du damit
> sagen wolltest, dass die Variable «x» im Kommandzeilenteil
>
> (echo $x)
>
> bereits vor dem Start des Subshells expandiert ist und der
> Subshell also in Wirklichkeit
>
> (echo X)
>
> zu sehen bekommt.  Verstehe ich dich damit richtig?

Ja. Aber den Irrtum meinerseits habe ich doch schon in meiner
ersten Antwort auf deinen Hinweis bestätigt und noch geschrieben,
wo die Spezifikation dies festlegt. Und da ich mich schon mal damit
beschäftigt habe, auch ein Beispiel gebracht, das darauf basiert.
Dein darauf folgendes Beispiel ist für mich einfach "straight forward".

Ich glaube nicht, dass dieses Subthema in der Gruppe wirklich von
Interesse ist, daher mal followup to poster (wenn's klappt).
0 nuovi messaggi