ubuntuusers.de

Zeilenanzahl im Terminal ermitteln

Status: Gelöst | Ubuntu-Version: Xubuntu 14.04 (Trusty Tahr)
Antworten |

hannes22

Anmeldungsdatum:
20. August 2009

Beiträge: 266

Moin,

ich hab mir 'nen Wolf geduckduckgot. Ich möchte in einem Terminal eine einfache Listenauswahl schreiben. Dafür muß ich wissen wieviele Zeilen dargestellt werden können. Ich schreibe das in C/C++ am liebsten mit Qt. Aber an der Zeilenfront scheint es mir etwas zu chaotisch zu sein.

Was ich bisher festgestellt habe:

Qt: frage nach der Zeilenzahl : nichts gefunden.

Im Termial mit bash:

1
echo $LINES

Liefert die Zeilenzahl. Wunderbar!

Unter Qt: mit der Klasse QProcess "echo $Lines" ausgeführt, liefert einenleeren String. (Kann ich mir erklären, QProcess erzeugt einen neuen Prozess ohne Fenster - das hat natürlich keine Zeilen).

In C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>      /* printf */
#include <stdlib.h>     /* getenv */

int main ()
{
  char* pPath;
  pPath = getenv ("LINES");
  if (pPath!=NULL)
    printf ("The current path is: %s",pPath);
  return 0;
}

liefert NULL.

Im Terminal:

1
set | grep LINES

liefert LINES=31

1
printenv |grep LINES

liefert nichts

Und jetzt brauche ich hilfe:

1.) Wie bekomme ich unter C die Zeilen eines Terminals?

2.) Warum unterscheiden sich set und printenv?

Danke schomal!

Hannes

lubux

Anmeldungsdatum:
21. November 2012

Beiträge: 14314

hannes22 schrieb:

Ich möchte in einem Terminal eine einfache Listenauswahl schreiben. Dafür muß ich wissen wieviele Zeilen dargestellt werden können.

Evtl. aus der config des Terminals. Z. B.:

:~$ cat /home/$USER/.config/lxterminal/lxterminal.conf | grep -i scrollback
scrollback=1500

Vain

Avatar von Vain

Anmeldungsdatum:
12. April 2008

Beiträge: 2505

Moin,

wenn du es direkt machen willst, dann benutzt du einen entsprechenden „ioctl“-Aufruf auf irgendeinem File-Deskriptor, der aufs Terminal zeigt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>

int
main()
{
    struct winsize w;

    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    printf("w = %d, h = %d\n", w.ws_col, w.ws_row);

    return 0;
}

Wenn du das Rad nicht neu erfinden möchtest, schau’ dir mal ncurses an.

Zu „$LINES“: Das ist eine Variable, die von der Bash gesetzt wird. Die wird aber afaik nicht exportiert und steht Kindprozessen daher nicht zur Verfügung. Du solltest/kannst dich daher nicht auf diese Variable verlassen. Warum sich „set“ und „printenv“ unterscheiden? Das erste ist ein Shell-Builtin und das zweite ein separates Programm (ergo Kindprozess):

$ type set
set is a shell builtin
$ type printenv
printenv is hashed (/usr/bin/printenv)

D630

Avatar von D630

Anmeldungsdatum:
24. Juli 2013

Beiträge: 329

In der Shell gingen noch tput and stty, die nicht LINES auslesen, sondern wohl direkt abfragen?!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ echo $SHELL
/bin/bash
$ type stty tput
stty is hashed (/bin/stty)
tput is hashed (/usr/bin/tput)
$ stty size
40 139
$ tput -S <<< lines$'\n'cols
40
139

:

hannes22

(Themenstarter)

Anmeldungsdatum:
20. August 2009

Beiträge: 266

Sehr interessant!

Danke für die Erläuterungen, und die Lösung natürlich.

Hannes

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6488

Wohnort: Hamburg

Noch eine kleine Ergänzung zu ioctl.

Wenn die Ausgabe in eine Datei umgelenkt wird, sind die Werte für ws_col und ws_row 0 was zu unangenehmen Überraschungen führen kann falls man darauf nicht vorbereitet ist.

Vain

Avatar von Vain

Anmeldungsdatum:
12. April 2008

Beiträge: 2505

Dakuan schrieb:

Noch eine kleine Ergänzung zu ioctl.

Wenn die Ausgabe in eine Datei umgelenkt wird, sind die Werte für ws_col und ws_row 0 was zu unangenehmen Überraschungen führen kann falls man darauf nicht vorbereitet ist.

Ja, oder was ganz Verrücktes:

$ ./bla >rofl
$ cat rofl
w = 22379, h = 42624

😀

In dem Zusammenhang kann man dann auch gleich noch die Funktion „isatty()“ erwähnen. Damit kannst du gucken, ob hinter STDOUT ein Terminal ist.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>

int
main()
{
    struct winsize w;

    if (isatty(STDOUT_FILENO))
    {
        ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
        printf("w = %d, h = %d\n", w.ws_col, w.ws_row);
    }
    else
        printf("stdout is not a terminal.\n");

    return 0;
}

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6488

Wohnort: Hamburg

Ja, oder was ganz Verrücktes:

Das überrascht mich jetzt aber doch. Da habe ich bisher wohl immer Glück gehabt. Allerdings muss ich auch sagen, das ich die winsize Struktur immer global (also quasi statisch) angelegt habe, womit sie wohl immer vom Compiler initialisiert wird.

Aber ich habe jetzt nochmal nachgesehen, wie ich das immer gemacht habe:

1
2
3
4
5
6
7
8
    ...
    /* Terminalgeometrie abfragen, im Fehlerfall Dummywerte setzen  */
    if( ioctl( STDOUT_FILENO, TIOCGWINSZ, &term_win ) == -1 ){
        term_win.ws_row = 24;
        term_win.ws_col = 80; /* wg. Ausgabeumleitung in Datei */
    }
    line_length = term_win.ws_col;
    ...

man sollte also zusätzlich noch den Fehlercode abfragen. Das mit isatty() werde ich bei mir aber trotzdem nachrüsten.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

Zusätzlich würde ich STDIN und STDERR probieren, falls STDOUT nicht mit einem Terminal verbunden ist.

Moment: wäre es nicht sogar noch besser, über das kontrollierende Terminal ("controlling terminal") zu gehen? Das müsste auch funktionieren, wenn STDIN, STDERR und STDOUT umgeleitet oder geschlossen sind.

Held-der-Arbeit

Anmeldungsdatum:
9. Januar 2006

Beiträge: 332

Hallo,

da ich letztens ein ähnliches Problem hatte, will auch mal meinen Senf dazugeben.

Ich habe das so gelöst:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>

int get_terminal_lines();
int get_terminal_cols();

int main()
{
  printf("Das Terminal hat %d Zeilen und %d Spalten.\n",get_terminal_lines(),get_terminal_cols());
  return 0;
}

int get_terminal_lines()
{
  FILE *fp;
  char lines[10];
  fp = popen("tput lines","r");
  fgets(lines,sizeof lines,fp);
  pclose(fp);
  return atoi(lines);
}

int get_terminal_cols()
{
  FILE *fp;
  char cols[10];
  fp = popen("tput cols","r");
  fgets(cols,sizeof cols,fp);
  pclose(fp);
  return atoi(cols);
}

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

Held-der-Arbeit schrieb:

Ich habe das so gelöst:

Da startest Du aber einen separaten Prozess. Abgesehen davon, dass das schief gehen kann, finde ich das ziemlich unelegant.

Ich habe mir mal die Mühe gemacht und ein komplettes Beispiel in C zusammen gehackt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char* argv[]) {
  char c_term [L_ctermid];
  FILE *f;

  /* find controlling terminal */
  ctermid(c_term);
  printf("cterm: %s\n", c_term);

  /* now find terminal size if possible */
  f = fopen(c_term, "w");

  if ( f ) {
    int fd = fileno(f);

    if ( isatty(fd) ) {
      struct winsize w;
      ioctl(fd, TIOCGWINSZ, &w);
      printf("rows: %4d columns: %4d\n", w.ws_row, w.ws_col);
    }
    else {
      dprintf(2, "Not a tty!\n");
    }

    fclose(f);
  }
  else {
    dprintf(2, "Cannot open %s\n", c_term);
  }

  return 0;
}

Die Ausgeben mit *printf sind nur zur Verdeutlichung. Die würde man dann in einem echten Programm weglassen. Für eine echte Anwendung müsste man das dann noch in eine Funktion auslagern, die vielleicht die Struct w oder NULL zurück liefert.

Das sieht dann so aus:

$ gcc -Wall -o ct-test ct.c && ./ct-test
cterm: /dev/pty0
rows:   60 columns:  132
$ stty -a | fgrep row
speed 38400 baud; rows 60; columns 132; line = 0; 

Der stty ist nur zur Kontrolle da. So sieht das dann aus, wenn man das Controlling Terminal abhängt:

$ setsid ./ct-test
$ cterm: no tty
Not a tty!
 

Held-der-Arbeit

Anmeldungsdatum:
9. Januar 2006

Beiträge: 332

Was kann denn genau schief gehen, wenn ich mit popen() arbeite?

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

Held-der-Arbeit schrieb:

Was kann denn genau schief gehen, wenn ich mit popen() arbeite?

Der Pfad wird nicht gefunden, der Prozess kann nicht geforkt werden, Du kannst keine Dateideskriptoren für die Pipes mehr anlegen... Die Ausgabe eines Programms zu parsen, wenn man über Syscalls an die Informationen kommt, ist doch total umständlich - vor allem, da Du Dir mehr Abhängigkeiten einhandelst.

Antworten |