Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Is there a way to do something like this:

tail -f logs/

and make the stdout to be updated on every line added to each file already present in logs/ and to every file that will be created in logs/ after the command is issued?

share|improve this question

I don't think there is a way with using only tail, but you should be able to achieve the same effect using watch together with tail. The only danger then becomes making sure you don't get a directory created instead of just a new file, which can be mitigated by making sure you use an appropriate shell glob passed to tail. Example: watch -n 2 tail *.log

share|improve this answer
Yes, I don't think there is a way with tail. But watch is far distant from what I'm looking for – Jack Sep 26 '13 at 14:00
I have used watch for several similar tasks and unfortunately it falls short. I think the biggest deficiency is my knowledge because I always seem to get errors where the command line seems to look good to me. – Madivad May 15 at 11:22
up vote 2 down vote accepted

Thanks all for support, but since nor mutitail nor tail -F nor watch tail seems to help for what I need, I developed a small solution in C. I post the code here, since maybe someone can find it useful. (There are missing checks and some weakness I know, but so far it's enough)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#include <linux/limits.h>
#define    CHAR_BACK   500

// * File handler structure
struct file_followed { long last_position; char filename[NAME_MAX]; struct file_followed * next; };
struct file_followed * file_list = NULL;

// * To quit peacefully
int cycle = 1;
void stopCycle(int u) { cycle = 0; }

// * Last tailed filename
char last_tailed[NAME_MAX];

void fileAdd(char * file) {
    struct file_followed ** list = &file_list;
    struct stat statdesc;

    if(stat(file, &statdesc) || !S_ISREG(statdesc.st_mode)) { return; }
    while(*list) { list = &((*list)->next); }
    *list = malloc(sizeof(struct file_followed));
    (*list)->last_position = -1;
    strcpy((*list)->filename, file);
    (*list)->next = NULL;

int fileTail(struct file_followed * item) {
    int ret = 0;
    FILE * fp = fopen(item->filename, "r");
    fseek(fp, 0, SEEK_END);
    long end_position = ftell(fp);

    if( end_position != item->last_position ) {
        if(strcmp(item->filename, last_tailed)) { strcpy(last_tailed, item->filename); printf("\n** %s **:\n", item->filename); }

        int start_position = item->last_position == -1 || item->last_position > end_position ? (end_position-CHAR_BACK > 0 ? end_position-CHAR_BACK : 0) : item->last_position;
        fseek(fp, start_position, SEEK_SET);

        int len = end_position - start_position;
        char * buf = malloc(len+1);
        fread(buf, len, 1, fp);
        buf[len] = '\0';
        printf("%s%s", len == CHAR_BACK ? "[...]" : "", buf);

        item->last_position = end_position;
        ret = 1;

    return ret;

void fileRem(char * file) {
    struct file_followed ** list = &file_list;
    while(*list && strcmp((*list)->filename, file)) { list = &((*list)->next); }
    if(*list) { struct file_followed * todel = *list; *list = (*list)->next; free(todel); }

int main(int argc, char ** argv) {

    struct dirent **namelist;
    struct stat statdesc;
    struct timeval tv;
    fd_set set;
    int fd;
    int wd;
    int r;

    // * Help
    if(stat(argv[1], &statdesc) || !S_ISDIR(statdesc.st_mode)) { printf("[usage] %s dir-to-monitor\n", argv[0]); exit(EXIT_FAILURE); }

    // * Init
    memset(last_tailed, 0, sizeof(last_tailed));
    signal(SIGINT, stopCycle);
    signal(SIGTERM, stopCycle);

    // * Inotify
    if( (fd = inotify_init()) < 0) { perror("inotify_init"); }
    if( (wd = inotify_add_watch( fd, ".", IN_CREATE | IN_DELETE ) < 0)) { perror("inotify_add_watch"); }

    // * File add recursively on dirscan
    if( (r = scandir(".", &namelist, 0, alphasort)) < 0) { perror("scandir"); }
    while (r--) { fileAdd(namelist[r]->d_name); free(namelist[r]); }

    // * Neverending cycle
    while(cycle) {
        // * Select on inotify
        FD_SET(fd, &set);
        tv.tv_sec = 0;
        tv.tv_usec = 1000;
        if( (r = select(fd+1, &set, NULL, NULL, &tv)) == -1) { perror("select"); }

        // * New add or del on inotify
        if(r) {
            struct inotify_event * event;
            char buf[1024];
            if(read(fd, buf, 1024) <= 0) { perror("read"); }
            event = (struct inotify_event *) buf;
            if(event->mask & IN_CREATE) { fileAdd(event->name); } 
            else if(event->mask & IN_DELETE) { fileRem(event->name); }

        // * Check for new tails
        struct file_followed * list = file_list;
        int tailers = 0;
        while(list) { tailers += fileTail(list); list = list->next; }
        if(!tailers) { usleep(500000); }

    // * Stop inotify
    inotify_rm_watch( fd, wd );

    return EXIT_SUCCESS;
share|improve this answer
You Sir, are a genius. Been looking for something like this for a while. I have the output of an app that is date logged and the current tail doesn't pick up new days. This works perfectly. Care to move this to github or similar and let it be improved upon (only based on what you said re checks). Did you base this on the code for tail originally, or is it built from the ground up? – Madivad May 4 at 0:22
Glad to find it helped someone. I wrote this from scratch, logic is quite simple thanks to inotify api.. so simple that I never thought to githubbing it. Feel free to do it if you think it's worth it – Jack May 4 at 9:28
G'day Sir Jack, I have taken the liberty to add this to github since it is one of the best little utilities I've come across in ages and I now have it running literally 24/7. Please feel free to add/change/TAKE CONTROL OF the repo and if you so desire, I will gladly hand it over to you (if you tell me how) since I don't know if I have the skills required to do it justice. Thanks again for answering a plaguing question of mine and possibly others :) Cheers! – Madivad May 15 at 11:20
You're great Madivad. I saw the page and loved it. You did very good, thanks! – Jack May 15 at 15:42

You could use: tail -F logs/*

Bonus tip: Check out multitail, it's a great little command.

For example: Merge ALL apache logfiles (*access_log/*error_log) into one window:

multitail -cS apache --mergeall /var/log/apache2/*access_log --no-mergeall \  
  -cS apache_error --mergeall /var/log/apache2/*error_log --no-mergeall

Show 5 logfiles while merging 2 and put them in 2 columns with only one in the left column:

multitail -s 2 -sn 1,3  /var/log/apache/access.log -I /var/log/apache/error.log \ 
  /var/log/messages /var/log/mail.log /var/log/syslog
share|improve this answer
Thanks for multitail, it's a great tool. Anyway none has still helped with original question. When you do "tail -F logs/*" shell expands the star with all files present currently in logs/; hence the file that will be created after the command is issued will not be read by tail – Jack Sep 27 '13 at 14:39
I have to add, I LOVE multitail, and is one of the first tools I install on any new system. Especially for all the /var/log files that you can want to monitor. But as with every tool that currently exists, there is no way to monitor NEW files that get created AFTER invocation. Except for @Jack 's post above as he accepted answer – Madivad May 15 at 11:25

You might be able to use something like multitail to tail multiple files at the same time. If you combine that with the -F option (follow with retry, in case the file doesn't exist) it could get you what you're looking for.

share|improve this answer
-F for multitail is for configfile – Jack Sep 27 '13 at 16:29

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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