[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: stderr en rouge dans un terminal



On 2005-09-27 09:50:58 +0200, Baptiste Mathus wrote:
> Mon but est de faire que le terminal affiche tout ce qui est envoyé
> dans stderr (que ce soit pas un script ou un programme système ou
> n'importe quoi d'autre, c-a-d pas forcément du code à moi) s'affiche
> dans une couleur différente de stdout. Des idées ?

Il me semble que du point de vue du terminal, c'est impossible:
tout finit dans /dev/tty et le terminal ne sait pas d'où les
données proviennent. Donc la seule solution serait que ce soit
fait en amont, en particulier au niveau du shell, ou alors au
niveau du noyau.

> (Pour les connaisseurs, la console d'eclipse le fait par défaut par
> exemple).

Je ne connais pas la console d'eclipse, mais elle fonctionne peut-être
différemment d'un terminal texte conventionnel.

> Ça me donne une idée tiens puisque vous parlez de codes ANSI, je
> regarderais ce soir s'il n'existe pas un système de callback pour
> bash : peut-être qu'il est possible d'automatiser le traitement de
> stdout et stderr par un programme personnalisé. Auquel cas, il
> serait alors très simple d'écrire un petit prog qui passe stderr en
> rouge avec une séquence ANSI...

Pour zsh, quelqu'un avait lancé la discussion dans la mailing-list
zsh-users en juin 2004. Vous pouvez voir dans les archives. Sujet:

  coloring STDERR to terminal

Une des premières solutions données par un développeur de zsh:

 coproc while read line; print '\e[91m'${(q)line}'\e[0m' > /dev/tty
 exec 2>&p

Ça permet de voir un peu le genre de choses qu'on peut faire, mais
il y a des effets secondaires: problèmes de bufferisation, problèmes
de contrôle de jobs, ordre dans lequel les données sont envoyées
dans stdout et stderr...

Les choses vont un peu mieux avec:

  exec 2>>(while read line; do
    print '\e[91m'${(q)line}'\e[0m' > /dev/tty; done &)

et un "sleep 0" (ou autre commande externe ne faisant rien) dans
la fonction precmd(). Mais stdout et stderr peuvent toujours se
retrouver désynchronisés, et il y a toujours un problème de buffer
à cause du read line qui doit lire une ligne entière. J'avais fini
avec cette solution:

  exec 2>>(colorize `tput bold; tput setaf 1` `tput sgr0` > /dev/tty &)

qui résout un certain nombre de problèmes, mais pas tout.
Le source colorize.c est attaché.

-- 
Vincent Lefèvre <vincent@vinc17.org> - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / SPACES project at LORIA
/* $Id: colorize.c 8526 2005-08-17 17:32:07Z lefevre $
 *
 * Colorize the standard input. Written for zsh stderr coloring.
 */

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/types.h>

#define BUFFSIZE 512

static volatile sig_atomic_t usr1;

static void sigusr1(int sig)
{
  usr1 = 1;
}

static void writepid(char *tmpfile)
{
  FILE *f;

  f = fopen(tmpfile, "w");
  if (f == NULL)
    {
      perror("colorize (fopen)");
      exit(EXIT_FAILURE);
    }
  fprintf(f, "%ld\n", (long) getpid());
  if (fclose(f) != 0)
    {
      perror("colorize (fclose)");
      exit(EXIT_FAILURE);
    }
}

int main(int argc, char **argv)
{
  pid_t zshpid = 0;
  char *begstr, *endstr;
  fd_set rfds;
  int ret;

  if (argc != 3 && argc != 5)
    {
      fprintf(stderr,
              "Usage: colorize <begstr> <endstr> [ <zshpid> <tmpfile> ]\n");
      exit(EXIT_FAILURE);
    }

  /* Assume that the arguments are correct. Anyway, it is not possible
     to check them entirely. */
  begstr = argv[1];
  endstr = argv[2];
  if (argc == 5)
    {
      /* To do the synchronization with the zsh prompt output...
         Seems to be useless in practice, hence the argc == 3 case. */
      zshpid = atol(argv[3]);
      signal(SIGUSR1, sigusr1);
      writepid(argv[4]);
    }

  fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);

  /* To watch stdin (fd 0). */
  FD_ZERO(&rfds);
  FD_SET(0, &rfds);

  for (;;)
    {
      ret = select(1, &rfds, NULL, NULL, NULL);

      if (ret < 0 && errno != EINTR)
        {
          perror("colorize (pselect)");
          exit(EXIT_FAILURE);
        }

      if (ret > 0)
        {
          static unsigned char buffer[BUFFSIZE];
          static int dontcol = 0;
          ssize_t n;

          while ((n = read(0, buffer, BUFFSIZE)) >= 0)
            {
              ssize_t i;

              if (n == 0)
                return 0;  /* stdin has been closed */
              for (i = 0; i < n; i++)
                {
                  if (buffer[i] == 27)
                    dontcol = 1;
                  if (buffer[i] == '\n')
                    dontcol = 0;
                  if (!dontcol)
                    fputs(begstr, stdout);
                  putchar(buffer[i]);
                  if (!dontcol)
                    fputs(endstr, stdout);
                }
            }
          fflush(stdout);
        }

      if (usr1)
        {
          usr1 = 0;
          if (kill(zshpid, SIGUSR1) != 0)
            {
              perror("colorize (kill)");
              exit(EXIT_FAILURE);
            }
        }
    }
}

Reply to: