How can I change the output from tail to use null-terminated lines instead of newlines?

My question is similar to this one: How to do `head` and `tail` on null-delimited input in bash?, but differs in that I want to do something like:

tail -f myFile.txt | xargs -i0 myCmd {} "arg1" "arg2"

I am not using find, and so cannot use -print0

All this is to avoid the error that occurs in xargs:

xargs: unmatched double quote;
    by default quotes are special to xargs unless you use the -0 option

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
| improve this answer | |
  • I actually meant to use the -f option on tail, which continuously prints lines as they come in. I didn't think this would matter, but apparently it does. With the -f option your solution doesn't work. – Lars Mar 28 '14 at 9:23
  • The tail -f myFile.txt | xargs -r0i -d '\n' myCmd "{}" "||" "||" seems to work! Thanks. – Lars Mar 28 '14 at 9:31
  • 1
    @Lars, sorry, the -i option takes an optional argument, so -i0 would not work. They should all work with tail -f except the last one, but beware that because of buffering, there will be a delay before xargs gets its input from tr. You can change that by running stdbuf -o0 tr '\n' '\0' instead of tr '\n' '\0'. – Stéphane Chazelas Mar 28 '14 at 9:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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