Amir
Amir

Reputation: 541

Find files in a dir, executing a command with execdir and redirecting

It seems like I am unable to find a direct answer to this question. I appreciate your help.

I'm trying to find all files with a specific name in a directory, read the last 1000 lines of the file and copy it in to a new file in the same directory. As an example:

Find all files names xyz.log in the current directory, copy the last 1000 lines to file abc.log (which doesn't exist).

I tried to use the following command with no luck:

find . -name "xyz.log" -execdir tail -1000 {} > abc.log \;

The problem I'm having is that for all the files in the current directory, they all write to abc.log in the CURRENT directory and not in the directory where xyz.log resides. Clearly the find with execdir is first executed and then the output is redirected to abc.log.

Can you guys suggest a way to fix this? I appreciate any information/help.

EDIT- I tried find . -name "xyz.log" -execdir sh -c "tail -1000 {} > abc.log" \; as suggested by some of the friends, but it gives me this error: sh: ./tail: No such file or directory error message. Do you guys have any idea what the problem is?

Luckily the solution to use -printf is working fine.

Upvotes: 3

Views: 11697

Answers (3)

ctn
ctn

Reputation: 2930

The simplest way is this:

find . -name "xyz.log" -execdir sh -c 'tail -1000 "{}" >abc.log' \;

A more flexible alternative is to first print out the commands and then execute them all with sh:

find . -name "xyz.log" -printf 'tail -1000 "%p" >"%h/abc.log"\n' | sh

You can remove the | sh from the end when you're trying it out/debugging.

There is a bug in some versions of findutils (4.2 and 4.3, though it was fixed in some 4.2.x and 4.3.x versions) that cause execdir arguments that contain {} to be prefixed with ./ (instead of the prefix being applied only to {} it is applied to the whole quoted string). To work around this you can use:

find . -name "xyz.log" -execdir sh -c 'tail -1000 "$1" >abc.log' sh {} \;

sh -c 'script' arg0 arg1 runs the sh script with arg0, arg1, etc. passed to it. By convention, arg0 is the name of the executable (here, "sh"). From the script you can access the arguments using $0 (corresponding to "sh"), $1 (corresponding to find's expansion of {}), etc.

Upvotes: 5

twalberg
twalberg

Reputation: 62379

Here's another non-find (well, sorta - it still uses find but doesn't try to shoehorn find into doing the whole thing):

while read f
do
  d=$(dirname "${f}")
  tail -n 1000 "${f}" > "${d}/abc.log"
done < <(find . -type f -name xyz.log -print)

Upvotes: 0

lreeder
lreeder

Reputation: 12206

The redirect isn't passed into execdir, so abc.log shows up in the directory you run the command in. -execdir also doesn't like embedded redirects. but you can workaround the problem by passing -execdir a shell command with a redirect embedded, like this:

find . -name "xyz.log" -execdir sh -c '/usr/bin/tail -1000 {} > abc.log' \;

Much credit to this blog post (not mine):

http://www.microhowto.info/howto/act_on_all_files_in_a_directory_tree_using_find.html

Edit

I put the full path to tail in the command (assuming it's in /usr/bin on your system), since sh may load a .profile with a PATH that differs from your current shell.

Upvotes: 0

Related Questions