Bash Head and Tail – How to Use on Null-Delimited Input

findheadtailtext processingxargs

find command can output names of files as a null-delimited strings (if -print0 is provided), and xargs can consume them with -0 option turned on. But in between, it's hard to manipulate that collection of files – sort command has -z switch, that makes it possible to sort those files, but head and tail don't have them.

How can I do head and tail on those null-delimited inputs in a convenient way? (I can always create a short & slow ruby script, but I hope that there could be a better way)

Best Answer

GNU head and tail since coreutils version 8.25 have a -z option for that.

With older versions or for non-GNU systems, you can try and swap \0 and \n:

find ... -print0 |
  tr '\0\n' '\n\0' |
  head |
  tr '\0\n' '\n\0'

Note that some head implementations can't cope with NUL characters (and they're not required to by POSIX), but where find supports -print0, head and text utilities generally support NUL characters.

You can also use a function to wrap any command between the two trs:

nul_terminated() {
  tr '\0\n' '\n\0' | "$@" | tr '\0\n' '\n\0'
}

find ... -print0 | nul_terminated tail -n 12 | xargs -r0 ...

Keep in mind that under nul_terminated, a \0 means a newline character. So for instance, to replace \n with _:

find . -depth -name $'*\n*' -print0 | nul_terminated sed '
  p;h;s,.*/,,;s/\x0/_/g;H;g;s,[^/]*\n,,' | xargs -r0n2 mv

(\x0 being also a GNU extension).

If you need to run more than one filtering command, you can do:

find ... -print0 |
  nul_terminated cmd1 |
  nul_terminated cmd2 | xargs -r0 ...

But that means running a few redundant tr commands. Alternatively, you can run:

find ... -print0 | nul_terminated eval 'cmd1 | cmd2' | xargs -r0 ...
Related Question