Combiner les entrées de plusieurs fichiers / canaux sans encombrer les lignes ni bloquer?

9

Existe-t-il un outil qui prendra l'entrée de plusieurs fichiers ou canaux et l'écrira sur stdout, sans bloquer les lectures, de sorte que les lignes d'entrée individuelles ressortent intactes? Je veux essentiellement multiplexer un tas d'entrées sur une sortie sans encombrer les lignes.

$ combine file1 <(prog2) ... > nice-output.txt
  1. Je me fiche de l'ordre de sortie
  2. Il ne doit pas bloquer tant qu'une entrée contient des données
  3. Il devrait être efficace (c.-à-d., Je peux voter contre votre one-liner Perl;)
Jay Hacker
la source

Réponses:

4

Vous devriez pouvoir le faire multitailassez facilement.

Caleb
la source
1
Pouvez-vous suggérer quels arguments j'utiliserais avec le multitail? Il ne semble pas avoir de mode non interactif, se bloque en essayant d'écrire sur stdout et bloque la lecture d'un tuyau.
Jay Hacker
Commencez par -Lpour exécuter une commande et fusionner la sortie avec le flux actuel et -aécrire la sortie dans un fichier. Je chercherai plus demain. Si vous donnez un exemple plus détaillé, je vais essayer de le travailler.
Caleb
4

Si les processus écrivent les lignes dans un seul writeappel, ce qui oblige les processus à utiliser la mise en mémoire tampon de ligne (généralement désactivée si leur sortie standard n'est pas un terminal), vous pouvez simplement les pointer tous vers un canal.

{ { sleep .1; echo one; sleep .1; echo two; } &
  { echo hello; sleep .15; echo world; };
  wait; } | cat

Si les processus n'effectuent que la mise en mémoire tampon des lignes lors de l'écriture sur un terminal, la méthode la plus simple est d'utiliser script. C'est un peu maladroit: il ne peut écrire que dans un fichier.

script -q -c '
    { { sleep .1; echo one; sleep .1; echo two; } &
      { echo hello; sleep .15; echo world; };
      wait; }'
tail -n +2 typescript

Si les programmes écrivent de longues lignes ou n'utilisent tout simplement pas la mise en mémoire tampon des lignes, cette approche ne fonctionnera pas. Vous aurez besoin d'un programme collecteur qui lit et met en mémoire tampon les lignes de chaque entrée séparément et effectue la synchronisation sur les fins de ligne. Il n'y a aucun utilitaire standard avec cette fonctionnalité. J'appuie la suggestion de Calebmultitail .

Voici un script Python qui lit les lignes produites par plusieurs commandes et les recrache sur sa sortie standard, sans casser une ligne. Je ne l'ai pas beaucoup testé, alors avertissez l'utilisateur. Je ne l'ai pas du tout évalué.

#!/usr/bin/env python
import Queue, itertools, os, subprocess, sys, threading
# Queue of (producer_id, line). line==None indicates the end of a producer.
lq = Queue.Queue()

# Line producer
def run_task(i, cmd):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
    line = p.stdout.readline()
    while line <> "":
        lq.put((i, line))
        line = p.stdout.readline()
    lq.put((i, None))

# Start a producer for each command passed as an argument
for i in range(1,len(sys.argv)):
    threading.Thread(target=run_task, args=(i, sys.argv[i])).start()
sources = len(sys.argv) - 1
# Consumer: print lines as they come in, until no producer is left.
while sources > 0:
    (k, line) = lq.get()
    if line == None: sources -= 1
    else: sys.stdout.write(str(k) + ":" + line)

Exemple d'utilisation:

./collect.py 'sleep 1; ls /; sleep 1; ls /' \
             '/bin/echo -n foo; sleep 1; /bin/echo -n bar; sleep 1; /bin/echo qux'
Gilles 'SO- arrête d'être méchant'
la source
1

Ouais, le multitail semble lié à la notion de "fenêtre" en tant que sous-ensemble d'un terminal; Je ne pouvais pas le faire jouer bien en tant que composant de pipeline.

Alors semble que nous ne nous hafta cette phalanges fissures

/* Copyright © 2015 [email protected]
** Use/modify as you see fit but leave this attribution.
** If you change the interface and want to distribute the
** result please change the binary name too! */
#include <err.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>

/* typedefs are for pussies */
struct {
    char *filename; /* for clarity of errors */
    char *data;
    long len;
    long cap;
} saved[FD_SETSIZE] = {0};

void
ewriten(int fd, char *buf, int n)
{
    int done = 0, c;
    while (done < n) {
        if ((c=write(fd, buf + done, n - done)) <= 0 && errno != EINTR) {
            err(1, "write");
        }
        done += c;
    }
}

int
empty(fd_set *fdset, int maxfd)
{
    int i;
    for (i=0; i <= maxfd; i++) {
        if (FD_ISSET(i, fdset)) return 0;
    }
    return 1;
}

void
combine(fd_set *fdset, int maxfd)
{
    char buf[4096], *cp;
    fd_set ready;
    int n, i, fd, left;
    while (!empty(fdset, maxfd)) {
        ready = *fdset;
        /* timeouts are for pussies */
        if (select(maxfd + 1, &ready, NULL, NULL, NULL) == -1) err(1, "select");
        for (fd=0; fd <= maxfd; fd++) {
            if (!FD_ISSET(fd, &ready)) continue;

            switch (n=read(fd, &buf, sizeof(buf))) {
            case -1:
                if (errno == EINTR)
                    break; /* ignore interrupts; we'll re-read next iteration */
                if (saved[fd].filename) err(1, "read: %s", saved[fd].filename);
                err(1, "read: %d", fd);
            case 0:
                if (saved[fd].len > 0) {
                    /* someone forgot their newline at EOF... */
                    ewriten(1, saved[fd].data, saved[fd].len);
                    saved[fd].data[0] = '\n'; /* put it back for them */
                    ewriten(1, saved[fd].data, 1);
                }
                free(saved[fd].data);
                FD_CLR(fd, fdset);
                break;
            default:
                for (cp=buf + n - 1; cp >= buf && *cp != '\n'; cp--); /* find last newline */
                left = n - (cp - buf + 1);
                if (cp >= buf) {
                    /* we found one! first dump any saved data from the last read */
                    if (saved[fd].len > 0) {
                        ewriten(1, saved[fd].data, saved[fd].len);
                        saved[fd].len = 0;
                    }
                    ewriten(1, buf, cp - buf + 1);
                }
                if (left > 0) {
                    /* now save any leftover data for later */
                    int need = saved[fd].len + left;
                    if (saved[fd].cap < need &&
                       (saved[fd].data=realloc(saved[fd].data, need)) == NULL) {
                        errx(1, "realloc: failed on %d bytes", need);
                        /* it was good enough for quake... */
                    }
                    saved[fd].cap = need;
                    memcpy(saved[fd].data + saved[fd].len, buf + n - 1 - left, left);
                    saved[fd].len += left;
                }
            }
        }
    }
}

void
addfd(int fd, fd_set *fdset, int *maxfd)
{
    FD_SET(fd, fdset);
    if (*maxfd < fd) {
        *maxfd = fd;
    }
}

int
main(int argc, char **argv)
{
    fd_set fdset;
    char **arg = argv + 1;
    char *cp;
    struct stat st;
    int fd, maxfd = -1;
    FD_ZERO(&fdset);
    while (*arg != NULL) {
        /* getopt is for pussies */
        if (strncmp("-u", *arg, 2) == 0) {
            *arg += 2;
            if (**arg == '\0' && *++arg == NULL ) errx(1, "-u requires argument (comma separated FD list)");
            /* reentrancy is for pussies */
            for (cp=strtok(*arg, ","); cp != NULL; cp=strtok(NULL, ",")) {
                fd = atoi(cp);
                if (fstat(fd, &st) != 0) err(1, "%d", fd);
                addfd(fd, &fdset, &maxfd);
            }
            arg++;
        } else if (strcmp("-", *arg) == 0) {
            if (fstat(0, &st) != 0) err(1, "stdin", fd);
            addfd(0, &fdset, &maxfd);
            saved[0].filename = "stdin";
            arg++;
        } else if (strcmp("--", *arg) == 0) {
            arg++;
            break;
        } else if (**arg == '-') {
            errx(1, "unrecognized argument %s", *arg);
        } else {
            break; /* treat as filename */
        }
    }
    /* remaining args are filenames */
    for (; *arg != NULL; arg++) {
        /* stdio is for pussies */
        if ((fd=open(*arg, O_RDONLY)) == -1) err(1, "open: %s", *arg);
        addfd(fd, &fdset, &maxfd);
        saved[fd].filename = *arg;
    }
    combine(&fdset, maxfd);
    return 0;
}

Ahhh ça faisait du bien.

(note: il est testé sur environ deux ensembles d'entrées. des bogues peuvent ou non exister)

sqweek
la source