3

I've been reading quite a few posts howto find files and copy those.

What I want to achieve. Copy all *.jpg files from every directory to one directory. To avoid overwriting my intention is to add or rather insert epoch time into the file name.

From what I have found up till now the most successful code is this one:

find . -type f -name '*.jpg' -exec cp -T {} /media/hvdw/'Local station'/jpg/{}-$(date +%s) \;

It executes fine in one directory, but as the cp command includes the path I get the following error:

cp: cannot create regular file '/media/hvdw/Local station/jpg/./VXIMAGES/2999  03.jpg-1618003427': No such file or directory
  • Question one: How can I copy the file with it's bare file name?
  • Question two: How can I insert epoch inside into the name of the file and not append it to the extension? like 2999 03-1618003427.jpg.
HvdW
  • 41

2 Answers2

1

As you discovered, -exec substitutes {} with the full pathname used by find.

You want to get the basename (i.e. the last component of the pathname) and then insert some string into it. For this you need a shell "inside" find.

You run a shell from find like this:

find … -exec sh -c '…' …

where the single-quoted argument just after sh -c contains shell code. You need to know how to do this safely. Never embed {} in the shell code. In your case the command may be like:

find . -type f -name '*.jpg' -exec sh -c '…' find-sh {} \;

Note: find-sh is explained here: What is the second sh in sh -c 'some shell code' sh?

Now we just need the right shell code that will manipulate the pathname and call cp. This answer of mine provides a way to remove some suffix, the syntax will be like ${variable%.jpg} in our case. The links there lead to places where the syntax to remove some prefix is documented. We will use ${variable##*/} to get the basename first.

find . -type f -name '*.jpg' -exec sh -c '
   file="$1"
   newname="${file##*/}"
   newname="${newname%.jpg}"
   cp -T -- "$file" "/media/hvdw/Local station/jpg/$newname-$(date +%s).jpg"
' find-sh {} \;

Note the whole shell code is single-quoted so the outer shell does not expand anything. In the context of the inner shell some things are double-quoted as they should be.

The above code runs one sh and one cp per matching file. We can improve performance by reducing the number of sh processes. The following variant uses -exec sh … {} + to pass multiple pathnames to sh:

find . -type f -name '*.jpg' -exec sh -c '
for file do
   newname="${file##*/}"
   newname="${newname%.jpg}"
   cp -T -- "$file" "/media/hvdw/Local station/jpg/$newname-$(date +%s).jpg"
done
' find-sh {} +

Final notes:

  • Fast enough cp will cause the output from date +%s to be non-unique. Just in case consider date +%s.%N if your date supports %N.
  • -name '*.jpg' is case-sensitive and .jpg is hardcoded twice in our shell code. If you allow other extensions somehow (e.g. by using non-portable case-insensitive -iname '*.jpg' instead) then you will need to improve the shell code to handle extensions not known in advance. This is possible but I won't elaborate here*.
  • Double dash in cp -T -- "$file" … seems unnecessary because expanded $file cannot start with - because it must start with . because we used find . …. But

*) You or anyone else can ask a separate question if needed.

0

Thanks a lot @Kamil. I did adapt the code a little. Problem was that files placed in the jpg directory were scanned as well. I excluded the jpg directory with -path ./jpg Here's the code:

cd /media/hvdw/'Local station'/
find . -path ./jpg -prune -o -type f -iname '*.jpg' -exec sh -c '
for file do
   newname="${file##*/}"
   newname="${newname%.jpg}"
   mv -T -- "$file" "/media/hvdw/Local station/jpg/$newname-$(date +%s).jpg"
done
' find-sh {} +
cd  ~/

BTW, put it in a shell script

HvdW
  • 41