If you want that last 10 lines:
tail myFile.txt | tr '\n' '\0' | xargs -r0i myCmd {} arg1 arg2
But with GNU xargs
, you can also set the delimiter to newline with:
tail myFile.txt | xargs -ri -d '\n' myCmd {} arg1 arg2
(-0
is short for -d '\0'
).
Portably, you can also simply escape every character:
tail myFile.txt | sed 's/./\\&/g' | xargs -I{} myCmd {} arg1 arg2
Or quote each line:
tail myFile.txt | sed 's/"/"\\""/g;s/.*/"&"/' | xargs -I{} myCmd {} arg1 arg2
If you want the 10 last NUL-delimited records of myFile.txt
(but then that wouldn't be a text file), you'd have to convert the \n
to \0
before calling tail
which would mean the file will have to be read fully:
tr '\n\0' '\0\n' < myFile.txt |
tail |
tr '\n\0' '\0\n' |
xargs -r0i myCmd {} arg1 arg2
Edit (since you changed the tail
to tail -f
in your question):
The last one above obviously doesn't make sense for tail -f
.
The xargs -d '\n'
one will work, but for the other ones, you'll have a buffering problem. In:
tail -f myFile.txt | tr '\n' '\0' | xargs -r0i myCmd {} arg1 arg2
tr
buffers its output when it doesn't go to a terminal (here, a pipe). I.E., it will not write anything until it has accumulated a buffer full (something like 8kiB) of data to write. Which means myCmd
will be called in batches.
On a GNU or FreeBSD system, you can alter the buffering behavior of tr
with the stdbuf
command:
tail -f myFile.txt | stdbuf -o0 tr '\n' '\0' |
xargs -r0i myCmd {} arg1 arg2
What about:
tail -f file1 & tail -f file2
Or prefixing each line with the name of the file:
tail -f file1 | sed 's/^/file1: /' &
tail -f file2 | sed 's/^/file2: /'
To follow all the files whose name match a pattern, you could implement the tail -f
(which reads from the file every second continuously) with a zsh
script like:
#! /bin/zsh -
zmodload zsh/stat
zmodload zsh/zselect
zmodload zsh/system
set -o extendedglob
typeset -A tracked
typeset -F SECONDS=0
pattern=${1?}; shift
drain() {
while sysread -s 65536 -i $1 -o 1; do
continue
done
}
for ((t = 1; ; t++)); do
typeset -A still_there
still_there=()
for file in $^@/$~pattern(#q-.NoN); do
stat -H stat -- $file || continue
inode=$stat[device]:$stat[inode]
if
(($+tracked[$inode])) ||
{ exec {fd}< $file && tracked[$inode]=$fd; }
then
still_there[$inode]=
fi
done
for inode fd in ${(kv)tracked}; do
drain $fd
if ! (($+still_there[$inode])); then
exec {fd}<&-
unset "tracked[$inode]"
fi
done
((t <= SECONDS)) || zselect -t $((((t - SECONDS) * 100) | 0))
done
Then for instance, to follow all the text files in the current directory recursively:
that-script '**/*.txt' .
Best Answer
That message is output on stderr like all warning and error messages.
You can either drop all the error output:
Or to filter out only the error messages that contain
truncate
:That means however that you lose the exit status of
tail
. A few shells have apipefail
option (enabled withset -o pipefail
) for that pipeline to report the exit status oftail
if it fails.zsh
andbash
can also report the status of individual components of the pipeline in their$pipestatus
/$PIPESTATUS
array.With
zsh
orbash
, you can use:But beware that the
grep
command is not waited for, so the error messages if any may end up being displayed aftertail
exits and the shell has already started running the next command in the script.In
zsh
, you can address that by writing it:That is discussed in the
zsh
documentation atinfo zsh 'Process Substitution'
: