Hari K Hari K - 4 months ago 18
Linux Question

Piping `find` to 'tail`

I want to get the last two lines of the find output and copy them somewhere. I tried

find . -iname "*FooBar*" | tail -2 -exec cp "{}" dest \;


but the output was "invalid option --2" for tail.

Also, my file or directory name contains spaces.

Answer

The following should work on absolutely any paths.

Declare a function to be able to use head and tail on NUL-separated output:

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

Then you can use it to get a NUL-separated list of paths from your search after passing through tail:

find . -exec printf '%s\0' {} \; | nul_terminated tail -n 2

You can then pipe that to xargs and add your options:

find . -iname "*FooBar*" -exec printf '%s\0' {} \; | nul_terminated tail -n 2 | xargs -I "{}" -0 cp "{}" "dest"

Explanation:

  1. find files in the current directory (.) and below with a name containing foobar (case insensitive because of the i in -iname);
  2. for each file, run (-exec) a command to
  3. print each file path ({}) followed by a NUL character (\0) individually (\;);
  4. swap newlines and NUL characters (tr '\0\n' '\n\0');"
  5. get the last two lines (that is, paths; tail -n 2, "$@");
  6. swap newlines and NUL characters again to get a NUL-separated list of file names (tr '\0\n' '\n\0').

The xargs command is a bit harder to explain. It builds as many cp ... "dest" commands as necessary to fit in the maximum command length of the operating system, replacing the {} token in the command with the actual file name (-I "{}" ... "{}"), using the NUL character as a separator when reading the parameters (-0).