bash 使用 tail -f 和 while read 监视文件并执行命令?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25712655/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Watch file and execute command using tail -f and while read?
提问by david_adler
I am trying to watch a file and execute a command onceevery time the file is changed, ideally with just native bash commands.
我想观看一个文件,执行命令一次,每次文件改变时,最好只用本地的bash命令。
This is as far as I have got, but how do I check if I have reached the beginning or end of the file? I realize that tail -f
doesn't read EOF so how I can tell that I have reached the end of the file?
这是我所知道的,但是我如何检查我是否到达了文件的开头或结尾?我意识到tail -f
它没有读取 EOF,所以我怎么知道我已经到达文件的末尾?
tail -f source_file.js | while read line || [[ -n "$line" ]];
# how do I execute a command here just **once**?
done
Answers which don't use tail
or while read
will be accepted as long as they are native bash commands and about one line.
不使用tail
或while read
将被接受的答案,只要它们是本机 bash 命令和大约一行。
Perhaps I could zero a variable every time while is called?
也许我可以在每次调用 while 时将变量归零?
回答by Tom Fenech
It's by no means a native bash solution but you could use libinotify
to do what you want:
它绝不是本机 bash 解决方案,但您可以libinotify
用来做您想做的事:
while inotifywait -qqe modify file; do
echo "file modified"
done
This watches for modifications to file
and performs the action within the loop whenever they happen. The -qq
switch suppresses the output of the program, which by default prints a message every time something happens to the file.
这会监视对file
循环中的操作的修改并在它们发生时执行。该-qq
开关抑制程序的输出,默认情况下,每次文件发生某些事情时都会打印一条消息。
回答by jm666
From the man:
来自男人:
-f
The -f option causes tail to not stop when end of file is reached, but rather to wait for additional data to be appended to the input.
-F
-f 选项使 tail 在到达文件末尾时不会停止,而是等待将其他数据附加到输入中。
So, if you want monitor and do actions, need break the script into two processes
所以,如果你想监控和做动作,需要把脚本分成两个进程
- one will show the content
tail
(if you want personally monitor the changes) - second will monitor changes and do actions
- 一个将显示内容
tail
(如果您想亲自监视更改) - 第二个将监视更改并执行操作
or
或者
- you should use for example
perl
orpython
what can monitor end-of-file and execute some actions when reach it (for example, run an bash script).
- 你应该使用例如
perl
或者python
什么可以监控档案结尾,当达到其执行某些操作(例如,运行bash脚本)。
The bash soultion can be based of file-modification time
bash 灵魂可以基于文件修改时间
file="./file"
runcmd() {
echo "======== file is changed ============"
}
#tail -f "$file" & #uncomment 3 lines is you want pesonally watch the file
#tailpid=$!
#trap "kill $tailpid;exit" 0 2 #kill the tail, when CTRL-C this script
lastmtime=0
while read -r mtime < <(stat -c '%Z' "$file")
do
if [[ $lastmtime != $mtime ]]
then
lastmtime=$mtime
runcmd "$file"
fi
sleep 1
done
added another solution based on standard perl
添加了另一个基于标准 perl 的解决方案
perltail() {
#adapted from the perlfaq5
#http://perldoc.perl.org/perlfaq5.html#How-do-I-do-a-tail--f-in-perl%3f
perl -MTime::HiRes=usleep -Mstrict -Mautodie -e '
$|=1;
open my $fh, "<", "$ARGV[0]";
my $curpos;
my $eof=1;
for (;;) {
for( $curpos = tell($fh); <$fh>; $curpos =tell($fh) ) {
print;
$eof=1
}
print "=EOF-reached=\n" if $eof;
$eof=0;
usleep(1000); #adjust the microseconds
seek($fh, $curpos, 0);
}' ""
}
eof_action() {
echo "EOF ACTION"
printf "%s\n" "${newlines[@]}" #new lines received from the last eof
newlines=() #empty the newlines array
}
perltail "./file" | while read -r line
do
if [[ $line =~ =EOF-reached= ]]
then
eof_action
continue
fi
#do something with the received lines - if need
#for example, store new lines into variable for processing in the do_action and like
newlines+=($line)
done
Principe:
原则:
- the
perltail
bash function runs an perl implementation oftail -f
, and additionally to, when reached the end-of-file it prints an MARKto the output, here:=EOF-reached=
. - the bash
while read
looking for the MARKand run action only the the mark exists - e.g. only when the end-of-file reached.
- 在
perltail
bash函数运行的Perl实现的tail -f
,另外给,当到达档案结尾它将打印MARK到输出端,在这里:=EOF-reached=
。 - bash
while read
寻找MARK并运行动作,只有该标记存在 - 例如,仅当到达文件末尾时。
回答by sjnarv
I am not sure of your exact meaning of doing something "once every time a file is changed" or an implementation of "about one line". The tail
command is usually used in a line-oriented manner, so I guess that something like 500 lines of text appended to a file in a single write(2)
is an update worthy of only one invocation of you command.
我不确定您“每次更改文件时执行一次”或“大约一行”的实现的确切含义。该tail
命令通常以面向行的方式使用,所以我想像在单个文件中附加 500 行文本这样write(2)
的更新仅值得一次调用您的命令。
But what about, say tens of lines appended after delays of tens of milliseconds? How often do you wish the command be called?
但是,比如说在几十毫秒的延迟之后附加了几十行呢?您希望该命令多久被调用一次?
If libinotify
is available, per Mr. Fenech, use it. If you're trying to use somewhat more basic shell utilities, find(1)
can also be used to notice when one file has become newer than another.
如果libinotify
可用,按照 Fenech 先生的说法,使用它。如果您尝试使用更基本的 shell 实用程序,find(1)
也可用于注意一个文件何时比另一个文件更新。
For example, a script that watches a file, and runs a supplied command when it's updated, polling at 1-second intervals.
例如,监视文件并在更新时运行提供的命令的脚本,每隔 1 秒轮询一次。
#!/bin/sh
[ $# -ge 2 ] || { echo "Usage: $(basename struct inotify_event* inotifytools_next_event (int timeout)
) <file> <cmd...>" 1>&2; exit 1; }
[ -r "" ] || { echo ": cannot read" 1>&2; exit 1; }
FILE=; shift
FTMP=$(mktemp /tmp/$(basename "$FILE")_tsref.XXXXXX)
trap 'rm -f "$FTMP"' EXIT
touch -r "$FILE" "$FTMP"
while true; do
FOUT=$(find "$FILE" -newer "$FTMP") || exit $?
if [ "$FOUT" = "$FILE" ]; then
touch -r "$FILE" "$FTMP"
eval "$@"
else
sleep 1
fi
done
回答by novacik
Lot of already written to this question and probably need clarify some principes.
很多已经写到这个问题,可能需要澄清一些原则。
If you want wait to EOF while the monitor process reading lines (regardless what is it, e.g. tail
or anything other) you must specify the wait-interval, with other words, what is considered as "new lines" in the file.
如果您想在监视器处理读取行(不管它是什么,例如tail
或其他任何内容)时等待 EOF,您必须指定等待间隔,换句话说,文件中被视为“新行”的内容。
Imagine:
想象:
- you will reach EOF, but in 100 microseconds new line arrived, the process will read it and reach another EOF. Belong this new line to the previous block of lines? (probably yes).
- And what if the new line arrive in 5 seconds? This is probably a new block of lines.
- 您将到达 EOF,但在 100 微秒后新行到达,进程将读取它并到达另一个 EOF。这条新行属于前一行行吗?(大概是)。
- 如果新线路在 5 秒内到达怎么办?这可能是一个新的行。
So, as you can see, the specifying the time what you considering for the "new block" of lines is abosolutely necessary. With other words, if you reach EOF multiple times in an specified time interval - it mean one EOF only. (like reach EOF twice in 100 microseconds).
因此,如您所见,指定您为“新块”行考虑的时间是绝对必要的。换句话说,如果您在指定的时间间隔内多次达到 EOF - 这意味着只有一个 EOF。(例如在 100 微秒内达到两次 EOF)。
Therefore the tail -f
itself using 1 seconds timeoutas default, and in the GNU version you can change this with the -s
parameter. From the docs:
因此,tail -f
它本身默认使用1 秒超时,在 GNU 版本中,您可以使用-s
参数更改此设置。从文档:
-s, --sleep-interval=N
with -f, sleep for approximately N seconds (default 1.0) between iterations; with inotify and --pid=P, check process P at least once every N seconds
-s, --sleep-interval=N
使用 -f,在迭代之间休眠大约 N 秒(默认为 1.0);使用 inotify 和 --pid=P,至少每 N 秒检查一次进程 P
Also, you can check this in the source codeof the tail.
此外,您可以在尾部的源代码中检查这一点。
For the the inotify
libraryand inotofy-tools
the principe is the same. (And tail, (depends on of your distribution) can use inotofylib
itself)). For using the inotifylib
must call the function
对于inotify
库和inotofy-tools
原理是一样的。(和尾巴,(取决于您的发行版)可以使用inotofylib
自己))。对于使用inotifylib
必须调用函数
tail -f source_file.js 2>&1 > /dev/null | while read line; do echo 'do any command here'; done
Your program should call this function or inotifytools_next_events()
frequently; between calls to this function, inotify events will be queued in the kernel, and eventually the queue will overflow and you will miss some events. (see here).
你的程序应该inotifytools_next_events()
经常调用这个函数;在调用此函数之间,inotify 事件将在内核中排队,最终队列将溢出,您将错过一些事件。(见这里)。
Again, the time-interval is essential (and all common tools defaults it to 1 sec).
同样,时间间隔是必不可少的(所有常用工具都将其默认为 1 秒)。
About the solution what you triedwith -n $line
. It can't work, because the tail never returns an empty line, when reaching EOF. The tail simply return the last line what got, and waits for the new lines (and checks them in specified time-intervals).
关于你试图解决与-n $line
。它不能工作,因为当到达 EOF 时,尾部永远不会返回空行。尾部简单地返回最后一行得到的内容,并等待新行(并在指定的时间间隔内检查它们)。
Summary:
概括:
- you must specify the timeout on what you want check the EOF condition (probably 1 second should be OK, if not - change the sleep time of the above scripts.
- all above solutions are working and are OK
- and finally:
- 您必须指定要检查 EOF 条件的超时时间(可能 1 秒应该没问题,如果不是 - 更改上述脚本的睡眠时间。
- 以上所有解决方案都有效并且没问题
- 最后:
Answers which don't use tail or while read will be accepted as long as they are native bash commands and about one line.
不使用 tail 或 while read 的答案将被接受,只要它们是本机 bash 命令和大约一行。
isn't make any sense, because of the above.
由于上述原因,没有任何意义。
Hope this helps.
希望这可以帮助。
回答by david_adler
Provided the editor used is not using atomic save the this works
如果使用的编辑器未使用原子保存,则此方法有效
while read line; do
#code goes here
done < <(tail -f $FILE)
回答by Toasty
This will allow you to use tail:
这将允许您使用尾部:
##代码##However, the loop runs once for every line (not once per file change). The benefit over using < <(stat -c '%Z' "$file")
with sleep is that it catches every change. To do that with any reasonable certainty using stat
, you have to drop the sleep command, but that hogs CPU resources.
但是,循环对每一行运行一次(不是每次文件更改一次)。< <(stat -c '%Z' "$file")
与 sleep 一起使用的好处是它可以捕捉每一个变化。要使用 以任何合理的确定性做到这一点stat
,您必须删除 sleep 命令,但这会占用 CPU 资源。
You could probably use a global variable with additional logic in the loop to stop the main command from running again within "x lines and/or y seconds" to prevent the command from being run for each individual line, but that's still unreliable and quickly becomes cumbersome.
您可能可以在循环中使用具有附加逻辑的全局变量来阻止主命令在“x 行和/或 y 秒”内再次运行,以防止为每个单独的行运行该命令,但这仍然不可靠并且很快就会变成麻烦。
Your best bet really is to use inotifywait
(from inotify-tools
), since it sets up a listener for the file, instead of polling every second (or continuously) like you'd do with stat
.
您最好的选择确实是使用inotifywait
(from inotify-tools
),因为它为文件设置了一个侦听器,而不是像使用stat
.
[EDIT]
[编辑]
Alternatively, if there's some key word or sequence that's added to the file exactly once every time it's updated, just check for it with if $(grep --quiet "keyword" <<< $line)
and put your code inside the if statement.
或者,如果有一些关键字或序列在每次更新时都添加到文件中,只需检查它if $(grep --quiet "keyword" <<< $line)
并将您的代码放在 if 语句中。