Using both basename and full path in find -exec
Asked Answered
S

5

9

I'm having an adventure in the world of bash scripting with find today.

Say I'm looking to copy any png file in any subdirectory of /home/mine/Pictures to /home/mine/pngcoppies and rename it "copy[basename]"using find and -exec. This would require me to use both the full path name and the basename in the same exec command. My problem is that I don't know how to get the basename. (See below)

find /home/mine -iname "*.png" -exec cp {} /home/mine/pngcoppies/copy{what_do_I_enter_here?} \;

Note: The above isn't actually what I'm doing, but it's a fundamental example of the issue, so a workaround using some other method to achieve the same ends wouldn't really apply here. The question is fundamentally about find -exec and its use of basenames.

Thanks in advance!

Sluice answered 1/10, 2013 at 21:29 Comment(0)
G
18

To see what is going on when you execute the find, just type set -xv
-x : Prints commands and their arguments as they are executed.
-v : Prints shell input lines as they are read.

Here is what I have :

find . -name "*.xml" -exec echo {} \;

Gives the output:

./log.xml
./svnLog.xml

And when I try :

set -xv
find . -name "*.xml" -exec echo {} \;

I get :

find . -name "*.xml" -exec echo {} \;
+ find . -name '*.xml' -exec echo '{}' ';'
./log.xml
./svnLog.xml

And then find execute echo passing the found filename instead of the litteral : '{}'

but when you add something to the {} like below :

find . -name "*.xml" -exec echo something{} \;
+ find . -name '*.xml' -exec echo 'something{}' ';'
something{}
something{}

Here the echo is executed twice for the 2 xml files that I have and since there is no more '{}' is the parameter list of the exec, it is not going to be replaced. so we got the echo 'something{}' for each file found.

To deal with this, you can think about executing echo passing to it the filename as parameter like for example :

sh -xvc 'echo sothing/$0' filename

We already know what is -x and -v. -c is to get the command from the string after it (man sh)

so the result is :

sh -xvc 'echo somthing/$0' filename
+ sh -xvc 'echo somthing/$0' filename
echo somthing/$0
+ echo somthing/filename
sothing/filename

I used 'echo somthing/$0' between ' ' so that $0 don't get expanded by the current shell. try it with " " and you will see the expantion of $0 ;)

So to get back to your 'problem', the find should be formatted as below:

find . -name "*.xml" -exec sh -xvc 'echo sothing/$0' {} \;

And we will get :

find . -name "*.xml" -exec sh -xvc 'echo sothing/$0' {} \;
+ find . -name '*.xml' -exec sh -xvc 'echo sothing/$0' '{}' ';'
echo sothing/$0
+ echo sothing/./log.xml
sothing/./log.xml
echo sothing/$0
+ echo sothing/./svnLog.xml
sothing/./svnLog.xml

As we can see know, the find is going to execute the shell cammand echo sothing/$0 passing to it '{}' (replaced by the filename found by find) so we get the desired echo sothing/./log.xml

set +xv to remove the verbose mode and we can get :

find . -name "*.xml" -exec sh -c 'echo "cp $0 someWhereElse/$0"' {} \;
cp ./log.xml someWhereElse/./log.xml
cp ./svnLog.xml someWhereElse/./svnLog.xml

so in your case , you have just to execute the copy in a sub shell (add sh or bash or you favorit shell after the exec) and let find pass the filename as parapeter to the it ;)

find /home/mine -iname "*.png" -exec sh -c 'cp $0 /home/mine/pngcoppies/copy/$0' {} \;

Hope this can help, and execuse me for my English.

Grayback answered 5/3, 2014 at 14:21 Comment(1)
That's great advise with the subshell! I just suggest quoting the "$0" to allow spaces.Doriandoric
M
20

From man find: "The -execdir primary is identical to the -exec primary with the exception that utility will be executed from the directory that holds the current file. The filename substituted for the string ``{}'' is not qualified."

find /home/mine -iname "*.png" -execdir cp {} /home/mine/pngcoppies/copy{} \;
Maui answered 3/10, 2013 at 0:29 Comment(2)
This is the one for me.Dicot
@Makkou's answer is good and an excellent explanation for one of the most maddening phenomenon in POSIX history, but this one makes the code a little bit more readable and solves the problem for a large class of cases. However, it's great to remember the more general solution for those edge cases.Sheffie
G
18

To see what is going on when you execute the find, just type set -xv
-x : Prints commands and their arguments as they are executed.
-v : Prints shell input lines as they are read.

Here is what I have :

find . -name "*.xml" -exec echo {} \;

Gives the output:

./log.xml
./svnLog.xml

And when I try :

set -xv
find . -name "*.xml" -exec echo {} \;

I get :

find . -name "*.xml" -exec echo {} \;
+ find . -name '*.xml' -exec echo '{}' ';'
./log.xml
./svnLog.xml

And then find execute echo passing the found filename instead of the litteral : '{}'

but when you add something to the {} like below :

find . -name "*.xml" -exec echo something{} \;
+ find . -name '*.xml' -exec echo 'something{}' ';'
something{}
something{}

Here the echo is executed twice for the 2 xml files that I have and since there is no more '{}' is the parameter list of the exec, it is not going to be replaced. so we got the echo 'something{}' for each file found.

To deal with this, you can think about executing echo passing to it the filename as parameter like for example :

sh -xvc 'echo sothing/$0' filename

We already know what is -x and -v. -c is to get the command from the string after it (man sh)

so the result is :

sh -xvc 'echo somthing/$0' filename
+ sh -xvc 'echo somthing/$0' filename
echo somthing/$0
+ echo somthing/filename
sothing/filename

I used 'echo somthing/$0' between ' ' so that $0 don't get expanded by the current shell. try it with " " and you will see the expantion of $0 ;)

So to get back to your 'problem', the find should be formatted as below:

find . -name "*.xml" -exec sh -xvc 'echo sothing/$0' {} \;

And we will get :

find . -name "*.xml" -exec sh -xvc 'echo sothing/$0' {} \;
+ find . -name '*.xml' -exec sh -xvc 'echo sothing/$0' '{}' ';'
echo sothing/$0
+ echo sothing/./log.xml
sothing/./log.xml
echo sothing/$0
+ echo sothing/./svnLog.xml
sothing/./svnLog.xml

As we can see know, the find is going to execute the shell cammand echo sothing/$0 passing to it '{}' (replaced by the filename found by find) so we get the desired echo sothing/./log.xml

set +xv to remove the verbose mode and we can get :

find . -name "*.xml" -exec sh -c 'echo "cp $0 someWhereElse/$0"' {} \;
cp ./log.xml someWhereElse/./log.xml
cp ./svnLog.xml someWhereElse/./svnLog.xml

so in your case , you have just to execute the copy in a sub shell (add sh or bash or you favorit shell after the exec) and let find pass the filename as parapeter to the it ;)

find /home/mine -iname "*.png" -exec sh -c 'cp $0 /home/mine/pngcoppies/copy/$0' {} \;

Hope this can help, and execuse me for my English.

Grayback answered 5/3, 2014 at 14:21 Comment(1)
That's great advise with the subshell! I just suggest quoting the "$0" to allow spaces.Doriandoric
L
2

try something like this :

find  /home/mine -iname "*.png" -printf "%P\n " | xargs  -I % -n1 cp %  /home/mine/pngcoppies/copy% 
Lauer answered 1/10, 2013 at 21:48 Comment(1)
I really like this approach, but I'm getting find: -printf: unknown primary or operator on OSXBoisleduc
V
0

To get basename you use

basename $your_full_path

To get that path before the basename

dirname $your_full_path
Varnado answered 1/10, 2013 at 21:39 Comment(1)
Sorry to ask for spoonfeeding but how might I apply this to my instance of find?Sluice
M
0

You need to combine the first two answers

I was tailing everything in one directory into another directory with

find . -type f -exec sh -c 'tail -n 1000 $0 >../tail1000/$0.tail' {} \;

The first Answer gives

cp ./log.xml someWhereElse/./log.xml

or for my tail command

find . -type f -exec sh -c 'tail -n 1000 $0 >../tail1000/$0.tail' {} \;

tail -n 1000 ./filenane > ../tail1000/./filenane.tail

which surprisingly works but does not look like a nice path and I expect there are cases where a path/./morepath does something unexpected with some command.

Combining the answers gives

find . -type f -execdir sh -c 'tail -n 1000 $0 >../tail1000/$0.tail' {} \;

which executes

tail -n 1000 filenane > ../tail1000/filenane.tail

And looks much more likely to give the expected results.

Mountaintop answered 6/6, 2020 at 8:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.