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.

1

6 Answers

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).

6

You can try

cp $(find . -iname "*FooBar*" | tail -2 ) dest
0
find . -iname "*FooBar*"|tail -n2|xargs -i cp "{}" dest

Unfortunately this won't work with filenames that contain spaces or newlines.

1

Robin Green:

$ find . -iname "*FooBar*"|tail -n2|xargs -i cp "{}" dest
Unfortunately this won't work with filenames that contain spaces or newlines.

This will work (at least to the tail) if the file contains spaces. That's because the find will put each file on one line including spaces, tabs, and other special characters.

The problem is that xargs will not operate with spaces. You can use the -0 or --null option with xargs, but that was designed with find ... -print0 in mind.

What might work is using a while loop.

find . -iname "*FooBar*" | tail -n2 | while read file
do
   cp "$file" "$dest"
done

Since you're only reading in one item per line, the $file will contain the file name with all of the various characters. The only time this will not work is if $file contains a NL. Then, the tail command itself will have issues. Fortunately, having a NL in a file name is quite rare.

Some people do this:

while IFS=\n read file

which removes input separators on anything other than a NL, but that shouldn't be necessary.

Since You file contains 2 lines also have spaces lets keep inverted commas

while read line
do

 cp "$line" dest;

done < $(find . -iname "*FooBar*" | tail -2)

I would do:

find . -iname "filename" -exec tail -n 2 {} \; > output.txt

Your Answer

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

Robert Thorne

Robert Thorne

Automotive & Future Transportation Editor

Robert Thorne covers electric vehicle innovations, autonomous driving systems, global mobility trends, and automotive engineering developments.

Share this article
Twitter Facebook Pinterest