Skąd program wie, czy stdout jest podłączony do terminala lub rury?

12

Mam problem z debugowaniem programu segfault, ponieważ potrzebuję wyjścia bezpośrednio przed segfault, ale tracę go, jeśli przesyłam dane wyjściowe do pliku. Zgodnie z tą odpowiedzią: /unix//a/17339/22615 dzieje się tak , ponieważ bufor wyjściowy programu jest opróżniany natychmiast po podłączeniu do terminala, ale tylko w niektórych punktach po podłączeniu do potoku. Kilka pytań tutaj:

  • W jaki sposób program określa, do czego podłączony jest jego stdout?

  • W jaki sposób polecenie „skrypt” wywołuje takie samo zachowanie, jak podczas zapisywania programu w terminalu?

  • Czy można to osiągnąć bez polecenia skryptu?

mowwwalker
źródło
Powiązane pytanie to unix.stackexchange.com/q/513926/5132 .
JdeBP

Odpowiedzi:

23

Mówienie, jeśli deskryptor pliku wskazuje urządzenie końcowe

Program może stwierdzić, czy deskryptor pliku jest powiązany z urządzeniem tty, używając isatty()standardowej funkcji C (która ogólnie poniżej wykonuje niewinne ioctl()wywołanie systemowe specyficzne dla tty , które zwróci błąd z błędem, gdy fd nie wskaże urządzenia tty) .

Narzędzie [/ testmoże to zrobić za pomocą swojego -toperatora.

if [ -t 1 ]; then
  echo stdout is open to a terminal
fi

Śledzenie wywołań funkcji libc w systemie GNU / Linux:

$ ltrace [ -t 1 ] | cat
[...]
isatty(1)                                      = 0
[...]

Śledzenie wywołań systemowych:

$ strace [ -t 1 ] | cat
[...]
ioctl(1, TCGETS, 0x7fffd9fb3010)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]

Mówienie, jeśli wskazuje na rurę

Aby ustalić, czy fd jest powiązany z potokiem / fifo, można użyć fstat()wywołania systemowego , które zwraca strukturę, której st_modepole zawiera typ i uprawnienia pliku otwartego na tym fd. W tym polu można użyć S_ISFIFO()standardowego makra C , st_modeaby ustalić, czy fd jest potokiem / fifo.

Nie ma standardowego narzędzia, które mogłoby to zrobić fstat(), ale istnieje kilka niekompatybilnych implementacji statpolecenia, które może to zrobić. zshjest statwbudowany w stat -sf "$fd" +modektóre powraca do trybu jako ciąg znaków, który pierwszy znak oznacza rodzaj ( pna rurę). GNU statmoże zrobić to samo stat -c %A - <&"$fd", ale musi również stat -c %F - <&"$fd"zgłosić ten typ sam. Z BSD stat: stat -f %St <&"$fd"lub stat -f %HT <&"$fd".

Mówienie, jeśli to możliwe

Aplikacje na ogół nie dbają o to, czy stdout jest fajką. Mogą się martwić, że jest to widoczne (choć ogólnie nie decyduje się na buforowanie, czy nie).

Aby sprawdzić, czy można zobaczyć fd (potoki, gniazda, urządzenia tty nie są widoczne, zwykłe pliki i większość urządzeń blokowych ogólnie jest), można spróbować wykonać względne lseek()wywołanie systemowe z przesunięciem 0 (tak nieszkodliwe). ddjest standardowym narzędziem, które jest interfejsem, lseek()ale nie można go użyć do tego testu, ponieważ implementacje nie zadzwoniłyby lseek()wcale, jeśli poprosiłbyś o przesunięcie 0.

Te zshi ksh93powłok posiada wbudowane poszukuje operatorów choć:

$ strace -e lseek ksh -c ': 1>#((CUR))' | cat
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
ksh: 1: not seekable
$ strace -e lseek zsh -c 'zmodload zsh/system; sysseek -w current -u 1 0 || syserror'
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
Illegal seek

Wyłączanie buforowania

scriptRozkaz wykorzystuje parę Pseudoterminal uchwycić wyjście programu, tak standardowe wyjście programu (i standardowe wejście i stderr) będzie urządzenie pseudoterminal.

Kiedy standardowe wyjście znajduje się na urządzeniu końcowym, nadal istnieje ogólne buforowanie, ale jest ono oparte na linii. printf/ putsi co nie napisze nic, dopóki nie zostanie wypisany znak nowej linii. W przypadku innych typów plików buforowanie odbywa się według bloków (o wielkości kilku kilogramów).

Istnieje kilka opcji, aby wyłączyć buforowanie, które zostały omówione w szeregu pytań i jak tutaj (szukaj unbuffer lub stdbuf , Nie można przekierować dane wyjściowe cięcie daje kilka podejść) albo za pomocą pseudo-terminal, jak można to zrobić przez socat/ script/ expect/ unbuffer( expectskrypt) / zshzptylub przez wstrzyknięcie kodu do pliku wykonywalnego, aby wyłączyć buforowanie, tak jak zrobiono to w GNU lub FreeBSD stdbuf.

Stéphane Chazelas
źródło
1
Świetna odpowiedź, bardzo za to dziękuję!
mowwwalker
Innym podejściem specyficznym dla Linuksa jest przeglądanie /prockatalogu i dla każdego /proc/<integer>/katalogu wyszukiwanie /proc/<integer>/fd/i znajdowanie deskryptora pliku, który ma ten sam numer i-węzła w pipefs serverfault.com/q/48330/363611 Jest to jednak przydatne tylko w skryptach, gdy nie można użyć opisanych wywołań systemowych w odpowiedzi Stephane'a, i jest to bardziej obejście niż właściwe rozwiązanie IMHO
Sergiy Kolodyazhnyy
Na BSD, lseekodniesie sukces na terminalach i innych urządzeniach znakowych, i po prostu ponownie ustawi licznik, który jest zwiększany przy każdym udanym read (). Nie wiem, czy to czyni je „widocznymi”.
mosvy
@mowwwalker Jeśli ta odpowiedź rozwiązała problem, poświęć chwilę i zaakceptuj, klikając znacznik wyboru po lewej stronie. To oznacza pytanie jako odpowiedź i jest to sposób wyrażania podziękowań na stronach Stack Exchange.
deser