Hari K
Hari K

Reputation: 169

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.

Upvotes: 7

Views: 11343

Answers (6)

Zener
Zener

Reputation: 11

I would do:

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

Upvotes: 0

l0b0
l0b0

Reputation: 58928

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

Upvotes: 5

David W.
David W.

Reputation: 107080

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.

Upvotes: 2

Ashish
Ashish

Reputation: 1952

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)

Upvotes: 1

Robin Green
Robin Green

Reputation: 33083

find . -iname "*FooBar*"|tail -n2|xargs -i cp "{}" dest

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

Upvotes: 2

gturri
gturri

Reputation: 14629

You can try

cp $(find . -iname "*FooBar*" | tail -2 ) dest

Upvotes: 4

Related Questions