0

when tailing a log file, we might use:

tail -f foo.log | grep bar

if we want to change the grep argument, we have to ctrl-c and then change the argument and then restart the command.

my question is - is there a way to dynamically change what we are grepping for without having to kill the tail/grep commands?

Also looking for a way to do this with less instead of tail if possible.

3
  • 2
    This belongs to Unix, SuperUser or one of the other sysadmin SE sites. But honestly, the question is asking for solution to a problem you don't mention. Why is it a problem to kill/ctl-c the process? Why do you need less? As it reads it sounds like: I'd like to load christmas tree on the roof without leaving the car. I would prefer to do it with Jaguar XKD (does not even have a roof). Sep 14, 2018 at 20:40
  • @alexanderMills could you explain why you need this? I wonder whether this is possible by using another tool, but I think that all of them have to be restarted. Please correct me if I am wrong, but you would like to replace bar without stopping the tail process?
    – 030
    Sep 14, 2018 at 21:30
  • Basically when writing stdout/stderr to a terminal instead of to a log file, I want to have one fewer terminal sessions open Sep 14, 2018 at 21:45

1 Answer 1

3

I implemented this in node.js - I don't know of a unix utility that can dynamically change grep. Here is how it works:

before dygrep:

many-child-procs > logfile.log                  # in terminal 1
tail -f logfile.log | grep <expression>          # in terminal 2

after dygrep:

many-child-procs | dygrep                    # in terminal 1

then you connect to dygrep with a tcp-client and control which expressions are filtered on.

The following node.js process reads from stdin, and uses a tcp connection to listen for expressions to filter on. For each line of stdin, it only forwards that to stdout if it matches one of the current regular expressions stored in memory.

#!/usr/bin/env node
'use strict';

import * as readline from 'readline';
import * as net from 'net';
import chalk from 'chalk';
import {createParser} from "./json-parser";
import log from './logger';

let port = 4900;


const rl = readline.createInterface({
  input: process.stdin.resume()
});

const regex = new Map<string, RegExp>();

rl.on('line', l => {

  if (regex.size < 1) {
    process.stdout.write(l + '\n');
    return;
  }

  for (let [k, v] of regex) {
    if (v.test(l)) {
      process.stdout.write(chalk.magenta(' (filtered) ') + l + '\n');
      break;
    }
  }

});

interface IncomingTCPMessage {
  command: {
    add: string,
    remove: string,
    list: boolean,
    removeall: boolean
  }
}

const server = net.createServer(s => {

  const sendMessage = (m: any) => {
    s.write(JSON.stringify({message: m}) + `\n`);
  };

  s.pipe(createParser()).on('data', (d: IncomingTCPMessage) => {

    if (!d.command) {
      log.error('No "command" field was found:', d);
      return ''
    }

    const c = d.command;

    if (c.list) {
      sendMessage({regexes: Array.from(regex.keys()).map(k => ({regex: regex.get(k), str: k}))});
      log.info('Listing all regex for the client.');
      return;
    }

    if (c.removeall) {
      regex.clear();
      sendMessage(`Cleared all regex.`);
      log.info('Cleared all regex.');
      return;
    }

    if (c.add) {
      regex.set(c.add, new RegExp(c.add));
      sendMessage(`Added regex: ${c.add}.`);
      log.info('Added regex:', c.add);
      return;
    }

    if (c.remove) {
      regex.delete(c.remove);
      sendMessage(`Deleted regex: ${c.remove}.`);
      log.info('Removed regex:', c.remove);
      return;
    }

    log.error('No matching field was found:', d);
    log.info('Regex:', regex);

  });

});

server.listen(port);

to see the complete code, see: https://github.com/ORESoftware/dygrep

Not the answer you're looking for? Browse other questions tagged or ask your own question.