27

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

1 Answer 1

38

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
3
  • 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
    Commented Mar 28, 2014 at 9:23
  • The tail -f myFile.txt | xargs -r0i -d '\n' myCmd "{}" "||" "||" seems to work! Thanks.
    – Lars
    Commented Mar 28, 2014 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'. Commented Mar 28, 2014 at 9:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .