Ich habe eine Dateieingabe:
$ cat input
1echo 12345
und ich habe folgendes programm
1. Version
#include <stdio.h>
#include <stdlib.h>
int main() {
system("/bin/bash -i");
return 0;
}
Wenn ich es jetzt laufen lasse,
$ gcc -o program program.c
$ ./program < input
bash: line 1: 1echo: command not found
$ exit
Alles funktioniert wie erwartet.
Jetzt möchte ich das erste Zeichen der Dateieingabe ignorieren, also rufe ich getchar()
vor dem Aufruf an system()
.
2. Version:
#include <stdio.h>
#include <stdlib.h>
int main() {
getchar();
system("/bin/bash -i");
return 0;
}
Überraschenderweise wird die Bash sofort beendet, als gäbe es keine Eingabe.
$ gcc -o program program.c
$ ./program < input
$ exit
Frage, warum bash die Eingabe nicht empfängt?
HINWEIS Ich habe einige Dinge ausprobiert und festgestellt, dass das Problem dadurch behoben wird, dass ein neues Kind für den Hauptprozess gegabelt wird:
3. Version
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
getchar();
if (fork() > 0) {
system("/bin/bash -i");
wait(NULL);
}
return 0;
}
$ gcc -o program program.c
$ ./program < input
$ 12345
$ exit
OS Ubuntu 16.04 64bit, gcc 5.4
-c
ohne Befehl ausgegeben wird ?Antworten:
Ein Dateistream ist definiert als :
Da Sie in die Standardeingabe umleiten, ist stdin nicht interaktiv und daher gepuffert.
getchar
ist eine Stream-Funktion und bewirkt, dass der Puffer aus dem Stream gefüllt wird, diese Bytes verbraucht und dann ein einzelnes Byte an Sie zurückgibt.system
führt nur fork-exec aus , sodass der Unterprozess alle geöffneten Dateideskriptoren unverändert erbt. Beimbash
Versuch, von der Standardeingabe zu lesen, wird festgestellt, dass sie sich bereits am Ende der Datei befindet, da der gesamte Inhalt bereits von Ihrem übergeordneten Prozess gelesen wurde.In Ihrem Fall möchten Sie nur dieses einzelne Byte verbrauchen, bevor Sie es dem untergeordneten Prozess übergeben.
So fügen Sie einen geeigneten Anruf vor dem
getchar()
:wird tun, was Sie wollen, indem Sie
stdin
auf ungepuffert setzen (_IONBF
).getchar
bewirkt, dass nur ein einzelnes Byte gelesen wird und der Rest der Eingabe für den Unterprozess verfügbar ist.read
In diesem Fall ist es möglicherweise besser, stattdessen die gesamte Streams-Schnittstelle zu verwenden.POSIX schreibt bestimmte Verhaltensweisen vor, wenn nach einem Fork von beiden Prozessen aus auf das Handle zugegriffen werden kann, merkt dies jedoch ausdrücklich an
was bedeutet, dass
system()
das nichts Besonderes damit zu tun hat, da es nur Fork-Exec ist .Dies ist wahrscheinlich das, was Ihre
fork
Problemumgehung trifft. Wenn der Griff auf beiden Seiten zugänglich ist, dann für den ersten :Das Aufrufen
fflush()
eines Lesestreams bedeutet Folgendes:Daher sollte die Deskriptorposition auf 1 Byte zurückgesetzt werden, genau wie die des Streams, und der nachfolgende Unterprozess erhält ab diesem Punkt seine Standardeingabe.
Zusätzlich für den zweiten (Kinder-) Griff :
und ich nehme an, "ein geeigneter Ort" könnte der gleiche sein (obwohl es nicht weiter spezifiziert ist). Der
getchar()
Aufruf "hat den Dateiversatz explizit geändert", daher sollte dieser Fall zutreffen. Die Absicht der Passage ist, dass das Arbeiten in beiden Zweigen der Gabel den gleichen Effekt haben sollte, also beidefork() > 0
undfork() == 0
das gleiche funktionieren sollten. Da in diesem Zweig jedoch tatsächlich nichts passiert, ist es fraglich, ob keine dieser Regeln für Eltern oder Kinder verwendet werden sollte.Das genaue Ergebnis ist wahrscheinlich plattformabhängig - zumindest wird nicht direkt angegeben, was als "jemals zugänglich" gilt, und welches Handle das erste und zweite ist. Es gibt auch einen früheren, übergeordneten Fall für den übergeordneten Prozess:
Dies gilt wohl für Ihr Programm, da es erst danach beendet wird. Wenn dies der Fall ist
fflush()
, sollten alle verbleibenden Fälle, einschließlich der , übersprungen werden, und das angezeigte Verhalten wäre eine Abweichung von der Spezifikation. Es ist fraglich, ob das Aufrufenfork()
eine Aktion für das Handle darstellt, aber nicht explizit oder offensichtlich, also würde ich dem nicht vertrauen. Es gibt auch genug "entweder" und "oder" in den Anforderungen, so dass viele Variationen zulässig erscheinen.Aus mehreren Gründen denke ich, dass das Verhalten, das Sie sehen, ein Fehler oder zumindest eine großzügige Interpretation der Spezifikation sein kann. Meine allgemeine Lesart ist, dass, da in jedem Fall ein Zweig der
fork
nichts tut, keine dieser Regeln angewendet werden sollte und die Deskriptorposition ignoriert werden sollte, wo sie war. Ich kann das nicht definitiv sagen, aber es scheint die einfachste Lektüre zu sein.Ich würde mich nicht darauf verlassen, dass die
fork
Technik funktioniert. Ihre dritte Version funktioniert hier bei mir nicht. Verwenden Siesetbuf
setvbuf
stattdessen / . Wenn möglich, würde ich sogarpopen
oder ähnlich verwenden, um den Prozess mit der erforderlichen Filterung explizit einzurichten, anstatt mich auf die Unklarheiten der Stream- und Dateideskriptor-Interaktionen zu verlassen.quelle
Ich begrüße Michael Homer für seine Antwort und dafür, dass er diese POSIX-Referenz gefunden hat. Aber ich glaube, dass ich mindestens 70% dieses Materials verstehe, und ich verstehe seine Antwort nicht vollständig - also habe ich diese TL; DR- Version dessen vorbereitet, was er meiner Meinung nach sagt.
getchar
(akagetc
) liest beim Lesen aus einer Datei tatsächlich einen Block aus der Datei (oder die gesamte Datei, je nachdem, welcher Wert geringer ist). Es liest Daten in einen Puffer. Sie erhalten dann das erste Zeichen aus dem Puffer.Obwohl der gepufferte Dateistream einen logischen E / A-Zeiger hat, der ein Zeichen in der Datei enthält, wird der E / A-Zeiger der Dateibeschreibung um einen Block in die Datei verschoben (oder im Fall Ihrer kleinen Datei nach) das Ende der Datei). Beim
system
Gabeln erbt der untergeordnete Prozess die Dateibeschreibung, nicht jedoch den Dateistream. Daher erbt bash einen Datei-E / A-Zeiger, der sich am Ende der Datei befindet. Daher erhält es beim Lesen aus der Datei nur einen EOF.main
wirdfork
direkt aufgerufen (anstatt nur aufzurufensystem
, was aufruftfork
). Und natürlich, wie in der zweiten Version Ihres Programms,main
Aufrufegetchar
. Das von Michael gefundene POSIX-Dokument besagt ausdrücklich, dass es eine Erweiterung des ISO C-Standards beschreibt. Soweit ich aus der kryptischen Sprache ersehen kann, heißt es, dassmain
(oder vermutlich der C-Compiler) dafür verantwortlich ist, das obige Problem zu bemerken, wennfork
es direkt von der Routine aufgerufen wird, die diestdio
Funktion (en) aufruft , und es zu lösen es. Daherfork
interagiert anscheinend (oder ein anderer Mechanismus?) Mit demstdio
Familie, um den physischen E / A-Zeiger der Dateibeschreibung zurückzuschieben, um ihn mit dem logischen E / A-Zeiger des Dateizeigers zu synchronisieren; dh ein Zeichen in die Datei. Die zweitefork
- die versteckte, die von aufgerufen wirdsystem
- profitiert zufällig davon, da die Shell die Datei ab dem zweiten Byte lesen kann.Ich habe die relevanten Manpages flüchtig durchsucht und konnte dieses beschriebene Verhalten nicht finden. Und ich konnte Ihr Ergebnis auch nicht reproduzieren. Dies mag also das von POSIX spezifizierte Verhalten sein, aber es ist eindeutig noch nicht universell implementiert.
quelle
fork
möglicherweise das Handle verwendet.Nasendämonen
man 3 getchar
sagt:Und
man 3 getc
sagt:Bash im interaktiven Modus verwendet wahrscheinlich
read
usw. (über Readline), um direkt auf die Eingabe zuzugreifen. Wir haben also Nasendämonen .quelle
bash
diestdio
Bibliothek verwendet wird, greift sie auf andere Puffer als den übergeordneten Prozess zu, sodass das Ergebnis immer noch undefiniert ist.