有没有办法做这样的事情:
tail -f logs/
并使 stdout 在 logs/ 中已经存在的每个文件中添加的每一行都进行更新和到将在日志中创建的每个文件/后命令发出了吗?
答案1
感谢大家的支持,但由于 mutitail、tail -F 和 watch tail 似乎都无法满足我的需求,因此我用 C 语言开发了一个小型解决方案。我将代码发布在这里,因为也许有人会觉得它有用。(我知道缺少检查和一些弱点,但到目前为止已经足够了)
#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);
free(buf);
item->last_position = end_position;
ret = 1;
}
fclose(fp);
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
chdir(argv[1]);
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]); }
free(namelist);
// * Neverending cycle
while(cycle) {
// * Select on inotify
FD_ZERO(&set);
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 );
close(fd);
return EXIT_SUCCESS;
}
答案2
我做了以下更改https://serverfault.com/a/542580/203373
修复我的系统(使用 Ubuntu Linux)上的几个编译错误。我添加了强制类型转换到(struct file_followed*)
和(char*)
,并将其包含
IN_MODIFY
在添加监视列表中以监视对当前文件的修改。添加了以下行:
if(event->mask & IN_MODIFY) { fileMod(event->name, file_list); }
和fileMod
函数
void fileMod(char* fileName, struct file_followed* file_list)
检查修改后的文件是否被截断,如果被截断则打印出来,并进行更新item->last_position = -1
以便重新打印该文件。
#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 = (struct file_followed*)malloc(sizeof(struct file_followed));
(*list)->last_position = -1;
strcpy((*list)->filename, file);
(*list)->next = NULL;
}
void fileMod(char* fileName, struct file_followed* file_list) {
struct file_followed* item = file_list;
while(item) {
if(strcmp(item->filename, fileName) == 0) {
FILE* fp = fopen(item->filename, "r");
fseek(fp, 0, SEEK_END);
long end_position = ftell(fp);
fclose(fp);
if (end_position <= item->last_position) {
printf("\n** %s truncated **\n", fileName);
item->last_position = -1;
}
usleep(100);
return;
}
item = item->next;
}
}
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 = (char*)malloc(len+1);
fread(buf, len, 1, fp);
buf[len] = '\0';
printf("%s%s", len == CHAR_BACK ? "[...]" : "", buf);
free(buf);
item->last_position = end_position;
ret = 1;
}
fclose(fp);
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
chdir(argv[1]);
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_MODIFY |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]); }
free(namelist);
// * Neverending cycle
while(cycle) {
// * Select on inotify
FD_ZERO(&set);
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_MODIFY) { fileMod(event->name, file_list);}
else 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 );
close(fd);
return EXIT_SUCCESS;
}
答案3
我认为仅使用 是没有办法的,但使用和tail
应该能够达到同样的效果。唯一的危险就是确保你不会创建一个目录,而只是一个新文件,这可以通过确保使用传递给 tail 的适当 shell glob 来缓解。示例:watch
tail
watch -n 2 tail *.log
答案4
我需要一个 bash 脚本来做同样的事情。下面是我想到的。
改编自以下内容:
- 监视文件的创建/删除使用
inotifywait
:https://unix.stackexchange.com/a/323919 - 要在日志前面打印文件名,请使用
parallel
:https://unix.stackexchange.com/a/337779
流程描述:
- 首先跟踪目录中所有现有文件。
- 使用 inotifywait 捕获所有创建/删除事件。
- 停止跟踪已删除的文件并开始跟踪任何新文件。
下面是:
#!/bin/bash
LOGSDIR=logs/
# Tail existing logs
find "$LOGSDIR" -maxdepth 1 -type f | xargs parallel --tagstring "{}:" --line-buffer tail -n0 -f {} ::: &
# Monitor directory for create/delete events
inotifywait -q -m ${LOGSDIR} -e delete -e create | while read -r directory action file; do
if [ "$action" == "DELETE" ]; then
# Find and kill tail process for deleted file
pid=`pgrep -f "tail[^:]*${directory}${file}$"`
if [ $? -eq 0 ]; then
command=`ps -p ${pid} -o comm=`
[ "${command}" == "tail" ] && kill ${pid}
fi
elif [ "${action}" == "CREATE" ]; then
# Tail the newly created file
parallel --tagstring "{}:" --line-buffer tail -n +1 -f {} ::: ${directory}${file} 2>/dev/null &
fi
done