Wie erhalte ich den richtigen Namen des steuernden Terminals?

12

Wie kann man den richtigen Namen des steuernden Terminals (wenn es einen gibt, sonst einen Fehler) als Pfadnamen erhalten?

Mit "richtiger Name" meine ich nicht /dev/tty, was nicht von anderen willkürlichen Prozessen verwendet werden kann, um auf dasselbe Terminal zu verweisen. Ich bevorzuge die Antwort wenn möglich als einfachen Shell-Code (wie im folgenden Beispiel), ansonsten als C-Funktion.

Beachten Sie, dass dies auch dann funktionieren muss, wenn die Standardeingabe umgeleitet wird, damit das ttyDienstprogramm nicht verwendet werden kann: In einem not a ttysolchen Fall wird ein Fehler angezeigt, da ttynur der Dateiname des mit der Standardeingabe verbundenen Terminals gedruckt wird.

Unter Linux kann man verwenden:

echo "/dev/`ps -p $$ -o tty | tail -n 1`"

Dies ist jedoch nicht portierbar, da laut POSIX das Format des Terminalnamens nicht angegeben ist .

Bei C-Funktionen wird ctermid (NULL)zurückgegeben /dev/tty, was hier unbrauchbar ist.

Hinweis: Laut zshDokumentation sollte man dazu in der Lage sein

zsh -c 'echo $TTY'

Dies schlägt jedoch derzeit (Version 5.0.7) fehl, wenn sowohl die Standardeingabe als auch die Standardeingabe umgeleitet werden:

$ zsh -c 'echo $TTY > /dev/tty' < /dev/null
/dev/pts/9
$ zsh -c 'echo $TTY > /dev/tty' < /dev/null > /dev/null
/dev/tty
vinc17
quelle
@mikeserv Ich denke, dass die psLösung die meisten Systeme abdeckt (und whonicht mehr hilft als ps), möglicherweise mit etwas mehr Code, um den Bezeichner alleine zu behandeln (wie "04"). Ich habe mich gefragt, ob es eine noch tragbarere Lösung gibt.
vinc17
Es könnte mit vorgepaarten Sets zu tun haben - vielleicht auch mit den alten Pty-Paaren im bsd- Stil. Nicht alle Ptys sind UNIX 98-Typen. Wie auch immer, von man xterm: -Sccn Diese Option ermöglicht xtermdie Verwendung als E / A-Kanal für ein vorhandenes Programm ... Der Optionswert besteht aus einigen Buchstaben des Namens einer Pty, die im Slave-Modus verwendet werden soll, sowie der geerbten FD-Nummer. Wenn die Option ein "/" -Zeichen enthält, wird der pty-Name vom fd abgegrenzt.
Mikesserv
@mikeserv Beachten Sie, dass die Lösung nicht mit psder Busybox (die von Android, BTW verwendet wird) funktioniert , auch nicht unter GNU / Linux. Was meinst du mit " xtermkann damit umgehen 04"?
vinc17
busyboxist nicht POSIX-konform. toyboxmacht sich jedoch sehr gut.
Mikesserv

Antworten:

8

Das "steuernde Terminal" aka. ctty unterscheidet sich von " dem Terminal, mit dem ein Prozess interagiert".

Der Standardweg, um den Pfad von ctty zu erhalten, ist ctermid (3). Beim Aufrufen von In freebsd seit Release 10 wird ein tatsächlicher Pfad nachgeschlagen [1], während ältere freebsd und glibc-Implementierungen [2] bedingungslos "/ dev / tty"] zurückgeben.

ps (1) aus dem Linux procps 3.2.8-Paket, lesen Sie den numerischen Eintrag in / proc / * / stat [3] und ziehen Sie den Pfadnamen teilweise durch Raten ab [4, 5] aufgrund mangelnder [6]. .

Wenn wir jedoch nicht ausschließlich an der ctty interessiert sind, sondern an einem mit stdio verknüpften Terminal, gibt tty (1) den mit stdin verbundenen Terminalpfad aus, der mit identisch ist ttyname(fileno(stdin)) c , und eine Alternative ist readlink /proc/self/fd/0 .


Weniger wichtige Überlegungen zum bedingungslosen Verhalten von "/ dev / tty": Die Angaben besagen lediglich, dass die von ctermid zurückgegebene Zeichenfolge "bei Verwendung als Pfadname auf das aktuelle Steuerterminal verweisen" "anstelle eines einfachen" der Pfadname des aktuellen Steuerterminal ". Es könnte so interpretiert werden, dass "/ dev / tty" nicht das steuernde Terminal ist, sondern sich nur auf das steuernde Terminal bezieht, wenn derselbe Prozess es öffnet (3). Ein Verstoß gegen die Regel "Ein Terminal darf höchstens für eine Sitzung geeignet sein" [7].

Eine weitere Konsequenz ist, dass ctermid nicht ausfällt, wenn ich kein steuerndes Terminal habe - ein solches Versagen ist nach den Spezifikationen zulässig [8] -, sodass ich mich meiner Ungewissheit nur bewusst werden kann, bis ein nachfolgendes Öffnen fehlschlägt (3). Das ist in Ordnung, da die Spezifikationen auch besagen, dass ein Aufruf von open (3) nicht garantiert ist, um erfolgreich zu sein.

把 友情 留 在 无 盐
quelle
Dies ist nicht portabler als die psLösung, die ich in meiner Frage angegeben habe, da nicht alle Betriebssysteme über ein /procDateisystem verfügen . Beachten Sie, dass psselbst ein Readlink verwendet wird /proc/self/fd/2(was auch dann funktioniert, wenn der Standardfehler umgeleitet wird).
vinc17
1
bearbeitet. und ps readlink auf / proc / * / fd / 2, um nicht die ctty zu finden, sondern um zusätzliche Informationen zu suchen, um das numerische Terminal dem Pfad zuzuordnen, siehe Link [4] [5].
28 友情 留 在 无 28
1
Hervorragende Bearbeitung. In Bezug auf die ctty; Ich kann nicht für vinc17 sprechen, aber während Sie wahrscheinlich immer irgendwo schreiben können, muss nur eine Datei geöffnet bleiben, damit Ihre Prozessgruppe am Leben bleibt.
Mikesserv
1
@ vinc17 - wenn Sie eine beliebige Datei - Deskriptoren auf Ihrem CTTY öffnen , dann können Sie sie mit lesen tty. stderrist wahrscheinlich das beste, weil es spezifiziert offen r / w sein soll. Also tty <&2.
Mikesserv
1
Dass ein bestimmtes Terminal für höchstens eine Sitzung geeignet sein kann, führt nicht dazu, dass glibc für seine ständige ctermid()Rückkehr nicht konform ist "/dev/tty". Dieser Name bezieht sich immer auf das steuernde Terminal des Prozesses, der darauf zugreift , was je nach Sitzung variiert. Das Terminal ist sitzungsspezifisch, der Name, auf den zugegriffen wird, muss jedoch nicht sein.
PellMel
5

Die POSIX-Spezifikation sichert ihre Wetten in Bezug auf das Controlling-Terminal wirklich ab und definiert sie folgendermaßen:

  • Terminal steuern
    • Die Frage, welche von möglicherweise mehreren speziellen Dateien, die sich auf das Terminal beziehen, gemeint ist, wird in POSIX.1 nicht behandelt. Der Pfadname /dev/ttyist ein Synonym für das steuernde Terminal, das einem Prozess zugeordnet ist.

Das steht in der Definitionsliste - und das ist alles, was es gibt. In der allgemeinen Terminalschnittstelle wird jedoch noch mehr gesagt:

  • Ein Terminal kann als steuerndes Terminal zu einem Prozess gehören. Jeder Prozess einer Sitzung mit einem steuernden Terminal verfügt über dasselbe steuernde Terminal. Ein Terminal kann das steuernde Terminal für höchstens eine Sitzung sein. Das steuernde Terminal für eine Sitzung wird vom Sitzungsleiter auf implementierungsdefinierte Weise zugewiesen. Wenn ein Sitzungsleiter kein steuerndes Terminal hat und eine Terminalgerätedatei öffnet, die noch keiner Sitzung zugeordnet ist, ohne die Option O_NOCTTY zu verwenden (siehe open ()), wird durch die Implementierung definiert, ob das Terminal zum steuernden Terminal der Sitzung wird Führer.

  • Das steuernde Terminal wird während eines Funktionsaufrufs von fork () von einem untergeordneten Prozess geerbt. Ein Prozess gibt sein steuerndes Terminal auf, wenn er eine neue Sitzung mit dem erstelltsetsid()Funktion; Andere Prozesse in der alten Sitzung, die dieses Terminal als steuerndes Terminal hatten, haben es weiterhin. Nach dem Schließen des letzten Dateideskriptors im System (unabhängig davon, ob er sich in der aktuellen Sitzung befindet oder nicht), der dem steuernden Terminal zugeordnet ist, wird nicht angegeben, ob alle Prozesse, die dieses Terminal als steuerndes Terminal hatten, kein steuerndes Terminal mehr haben. Ob und wie ein Sitzungsleiter ein steuerndes Terminal wieder erwerben kann, nachdem das steuernde Terminal auf diese Weise aufgegeben wurde, ist nicht spezifiziert. Ein Prozess gibt sein steuerndes Terminal nicht einfach auf, indem er alle dem steuernden Terminal zugeordneten Dateideskriptoren schließt, wenn andere Prozesse es weiterhin geöffnet haben.

Es gibt eine Menge, die nicht spezifiziert ist - und ehrlich gesagt denke ich, dass es Sinn macht. Während das Terminal eine wichtige Benutzeroberfläche ist, gibt es in einigen Fällen auch alle möglichen anderen Dinge - wie die tatsächliche Hardware oder sogar eine Art Drucker -, aber in vielen Fällen ist es praktisch gar nichts - wie einxterm ein Emulator, der nur ein Emulator ist . Es ist schwer, dort spezifisch zu werden - und ich denke, es wäre sowieso nicht viel im Interesse von Unix, da Terminals viel mehr als Unix leisten.

Wie auch immer, POSIX ist auch ziemlich zweifelhaft, wie ps sich verhalten soll, wenn es um die Ctty geht.

Da ist der -aSchalter:

  • Schreiben Sie Informationen für alle Prozesse, die mit Terminals verknüpft sind. Implementierungen können Sitzungsleiter aus dieser Liste auslassen.

Groß. Sitzungsleiter können weggelassen werden. Das ist nicht sehr hilfreich.

Und -t:

  • Schreiben Sie Informationen für Prozesse, die mit Terminals verknüpft sind, die in der Termliste angegeben sind. Der Antrag stellt sicher, dass die Termliste ein einzelnes Argument in Form einer <blank>oder durch Kommas getrennten Liste ist. Terminal-IDs müssen in einem implementierungsdefinierten Format angegeben werden.

... was eine weitere Enttäuschung ist. Über XSI-Systeme heißt es jedoch weiter:

  • Auf XSI-konformen Systemen müssen sie in einer von zwei Formen angegeben werden: dem Dateinamen des Geräts (z. B. tty04) oder, wenn der Dateiname des Geräts mit beginnt tty, nur der nach den Zeichen folgenden Kennung tty (z. B. 04) .

Das ist ein bisschen besser, aber kein Weg. Auch auf XSI-Systemen gibt es den -dSchalter:

  • Schreiben Sie Informationen für alle Prozesse mit Ausnahme der Sitzungsleiter.

... was zumindest klar ist. Sie können den -output-Schalter auch mit der Formatzeichenfolge angeben tty, aber wie Sie bereits bemerkt haben, ist das Ausgabeformat implementierungsdefiniert. Trotzdem denke ich, dass es so gut ist, wie es nur geht. Ich denke, dass - mit viel Arbeit - die oben genannten Schalter in Kombination mit einigen anderen Dienstprogrammen Ihnen einen ziemlich guten Ballpark bringen können. Um ganz ehrlich zu sein, ich weiß nicht, wann / wie es für Sie kaputt geht - und ich konnte mir keine Situation vorstellen, in der es passieren würde. Aber ich denke wahrscheinlich, wenn wir hinzufügen fuserund findden Pfad überprüfen können.

exec 2<>/dev/null
ctty=$(sh -c 'ps -p "$$" -o tty=' <&2)
sid=$(sh -c 'ps -Ao pid= -o tty=|
      grep '"$ctty$"' | 
      grep -Fv "$(ps -do pid=)"'  <&2)
find / -type c -name "*${ctty##*/}*" \
       -exec fuser -uv {} \; 2>&1  |
grep ".*$ctty.*${sid%%"$ctty"*}"

Das /dev/nullZeug sollte nur zeigen, dass es funktionieren kann, wenn keine der suchenden Subshells 0,1,2 mit der CTTY verbunden hat. Wie auch immer, das druckt:

/dev/pts/3:          mikeserv   3342 F.... (mikeserv)zsh

Jetzt erhält das oben Genannte den vollständigen Pfad auf meiner Maschine, und ich kann mir vorstellen, dass dies in den meisten Fällen für die meisten Menschen der Fall ist. Ich kann mir auch vorstellen, dass es scheitern könnte. Es sind nur grobe Heuristiken.

Dies könnte wahrscheinlich aus vielen anderen Gründen fehlschlagen, aber wenn Sie sich auf einem System befinden, das es dem Sitzungsleiter ermöglicht, alle Deskriptoren an die ctty abzugeben und dennoch die Seite zu bleiben, wie es die Spezifikation zulässt, wird dies definitiv nicht helfen. Trotzdem denke ich, dass dies in den meisten Fällen eine ziemlich gute Schätzung erhalten kann.

Natürlich ist die einfachste zu tun , was , wenn Sie keine Deskriptoren zu Ihrem CTTY verbunden ist nur ...

tty <&2

...o.ä.

mikeserv
quelle