Using both basename and full path in find -exec - linux

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!

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.

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{} \;

try something like this :
find /home/mine -iname "*.png" -printf "%P\n " | xargs -I % -n1 cp % /home/mine/pngcoppies/copy%

To get basename you use
basename $your_full_path
To get that path before the basename
dirname $your_full_path

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.

Related

Linux find script result not appending to the output text file

I wrote small shell script, to identify the PDF file associate pages in my website.
It’s take the pdf source list url one by one, as an input and finding recursive in website content.
Problem is when I run the script find result not appending to the output file,
But when I take the find command and run in terminal/putty manually can see the result.
Script:
#!/bin/bash
filename="PDF_Search_File.txt"
while read -r line
do
name="$line"
echo "*******pdf******** - $name\n" >>output_pdf_new.txt
find . -type f -exec grep -l "$name" '{}' \; >>output_pdf_new.txt
echo "*******pdf******** - $name\n" >>output_pdf_new.txt
done < "$filename"
source list url input file (PDF_Search_File.txt)
/static/pdf/pdf1.pdf
/static/pdf/pdf2.pdf
/static/pdf/pdf3.pdf
--------------------
out put result file (output_pdf_new.txt)
./Search_pdf.sh
*******pdf******** - /static/pdf/pdf1.pdf\n
*******pdf******** - /static/pdf/pdf1.pdf\n
./Search_pdf.sh
*******pdf******** - /static/pdf/pdf2.pdf\n
*******pdf******** - /static/pdf/pdf2.pdf\n
./Search_pdf.sh
*******pdf******** - /static/pdf/pdf3.pdf\n
*******pdf******** - /static/pdf/pdf3.pdf\n
------------------------------------------
terminal/putty can see the result for below, when manually run the find.
find . -type f -exec grep -l "/static/pdf/pdf1.pdf" '{}' \;
./en/toyes/zzz/index.xhtml
./en/toyes/kkk/index.xhtml
--------------
but having issue with script , only out put the echo result as above output result .
Update
when i execute the script with bash -x , it's giving below result
[user#server1 generated_content]# bash -x Search_pdf.sh
+ filename=PDF_Search_File.txt
+ read -r line
+ name=$'/static/pdf/pdf1.pdf\r'
\n'cho '*******pdf******** - /static/pdf/pdf1.pdf
+ find . -type f -exec grep -l $'/static/pdf/pdf1.pdf\r' '{}' ';'
\n'cho '*******pdf******** - /static/pdf/pdf1.pdf
+ read -r line
+ name=$'/static/pdf/pdf2.pdf\r'
\n'cho '*******pdf******** - /static/pdf/pdf2.pdf
+ find . -type f -exec grep -l $'/static/pdf/pdf2.pdf\r' '{}' ';'
is something wrong here
+ find . -type f -exec grep -l $'/static/pdf/pdf2.pdf\r' '{}' ';'
find command should be like below , but it's taking as above when executing
find . -type f -exec grep -l "/static/pdf/pdf1.pdf" '{}' \;
Have you tried -e option in echo to enable interpretation of backslash escapes?
Also why don't you simply do find | grep?
find ./ -type f | grep "$name" >> output_pdf_new.txt
Try following (./ instead of .) in find
find ./ -type f -exec grep -l "$name" '{}' \; >>output_pdf_new.txt
grep -rl for the file inside of your for loop:
cd /www/webroot/
grep -rl "${name}" * | while read file_path; do
# I need to do something with each file
echo $file_path
done
OR I just need to run the output to file
cd /www/webroot/
grep -rl "${name}" * >> output_pdf_new.txt

How to capitalize only first letter in filename using tr or mv on Linux?

Using terminal I need to write a command with mv/tr/find to:
Find files with names that start with a, o or e: [a,o,e]*.
Change those filenames first letters from lower to capital using tr or mv.
Everything has to be in one command not script. I've tried something like this:
find -name "[a,o,e]*" -exec {} mv [a,o,e]* [A,O,E]* \;
but it doesn't work.
This is a sample with some of my files starting with letters p and g:
$ find . -name '[pg]*.py'
./pycurl-example.py
./pymusic.py
./gtkmenu.py
You could use something like bellow, but unfortunatelly will not stop on the first occurence - it will translate all letters found:
$ find . -name '[pg]*.py' |tr '[pg]' '[PG]'
./Pycurl-examPle.Py
./Pymusic.py
./Gtkmenu.Py
Sinc tr has not an option to stop at first occurence like sed, you can make a trick like this:
$ find . -name '[pg]*.py' -exec bash -c 'echo -n "${0:2:1}"|tr 'pg' 'PG' && echo "${0:3}"' {} \;
Pycurl-example.py
Pymusic.py
Gtkmenu.py
Or even simplier without tr but with pure bash (idea from #Nominal Animal comment)
$ find . -name '[pg]*.py' -exec bash -c 'fn="${0:2}";echo "${fn^}"' {} \;
Pycurl-example.py
Pymusic.py
Gtkmenu.py
Or if your purpose is to do a real rename:
$ find . -name '[p]*c.py' -exec bash -c 'fn="${0:2}";mv -v "${fn}" "${fn^}"' {} \;
renamed 'pymusic.py' -> 'Pymusic.py'
And a more sophisticated rename that will work with any path:
$ find . -name 'g*.sh' -exec bash -c 'pn="${0%/*}";fn="${0##*/}";mv -v $0 ${pn}/${fn^}' {} \;
renamed './kalitools/Bluelog/scripts/gen_oui.sh' -> './kalitools/Bluelog/scripts/Gen_oui.sh'
renamed './greptest.sh' -> './Greptest.sh'
find . -name '[aoe]*' -exec sh -c 'echo mv "$1" "$(echo "$1"|sed -E "s,(.*/)(.),\1\u\2,")"' _ {} ';'
Remove the first "echo" if it looks like it's going to do what you want.
In my tmp dir with all kinds of crap in it, some of the output is:
mv ./a b c ./A b c
mv ./a.awk ./A.awk
mv ./a.prop ./A.prop
mv ./a1234 ./A1234
mv ./event.logger ./Event.logger
mv ./example.txt ./Example.txt
mv ./link/a ./link/A
mv ./link/a~ ./link/A~
mv ./old.csv ./Old.csv
mv ./rename/one-dash ./rename/One-dash
Try this:
for f in `find . -name "*" -type f|sed "s/^.\///"`; do f1stc=$(echo ${f::1}|tr [a-z] [A-Z]); fallbut1stc=$(echo ${f:1}); nf="${f1stc}${fallbut1stc}"; mv $f ${nf} 2>/dev/null || true; done

Find`s placeholder {} sed from variable1 and put into variable2

Then i use this script
for line in "`cat fromDirs.txt`";
do
find "$line" -type f \( -name '*good*' -o -exec grep -F "(NODES_'TASK')" {} \; \) -exec cp {} /tmp/ \;;
done
I get in folder /tmp only file names from where they are copied, but i need this filenames contains full paths from where they comes, im bored to trying fight with sed, please help
So i need just take each {} value and replace slash (/) with minus sign (-)
I trying many of variant but nothing good, this code do not work too
for line in "`cat fromDirs.txt`";
do
find "$line" -type f \( -name '*good*' -o -exec grep -F "(NODES_'TASK')" {} \; \) -exec cp {} /tmp/$(sed "s/\//-/g" <<< {}) \;;
done
file fromDirs.txt contains
/home/orders/
/etc/bin/school/
there are no output, just nothing haping, maybe beacause i use sh? i havent bash at all on system
I think the problem is in sed as it read placeholder {} as file instead of string, so if {} = /home/orders/good.php then sed open this file and change all slashes to minus sign, but i need to changeslashes only in filename so /home/orders/good.php -> -home-orders-good.php and then cp to /tmp/-home-orders-good.php
I guess you get problem since you double quote the output of the file.
Try change from:
for line in "`cat fromDirs.txt`";
to:
for line in `cat fromDirs.txt`;
or better (remove old and outdated back tics):
for line in $(cat fromDirs.txt);
best (use while to read the file):
#!/bin/bash
while read line
do
find "$line" -type f \( -name '*good*' -o -exec grep -F "(NODES_'TASK')" {} \; \) -exec cp {} /tmp/$(sed "s/\//-/g" <<< {}) \;;
done < fromDirs.txt

List all directories that contain a file with a particular extension

I need list all the directories that contain a file with .info extension in the first level.
--contrib
--abc
--ab.info
--def
--de.info
--xyz
--ab.gh
--ab.ij
The command should list
abc, def
This should work if you run it from your contrib directory:
find . -maxdepth 2 -name "*.info" -exec dirname {} \;
It will need more tweaking if you actually want to run it from the parent of contrib.
The above will give you:
./abc
./def
Which is not exactly what you wanted. So maybe something more like this will help:
find . -maxdepth 2 -name "*.info" -exec sh -c 'F=$(dirname {}) ; basename $F' \;
It is more convoluted but the result is:
abc
def
Or without basename and dirname:
find . -maxdepth 2 -name "*.info" -exec bash -c '[[ {} =~ .*/(.*)/.* ]] && echo ${BASH_REMATCH[1]}' \;
Or with sed:
find . -maxdepth 2 -name "*.info" -exec echo {} + | sed 's|./\(\S*\)/\S*|\1,|g'
Result:
abc, def,

Fetch the KSH NAME

find `pwd` -type f -name "*.ksh" -exec grep -H ".ksh" '{}' \; -print 2>/dev/null
On Executing this command , the result is
/home/edwprod/Partner_com/bin/edw_etl_job_load.ksh:# Program Name : edw_etl_job_load.ksh
/home/edwprod/Partner_com/bin/edw_etl_job_load.ksh
/home/edwprod/Partner_com/bin/exec_housekeep_main.ksh:# Program Name : exec_housekeep_main.ksh
/home/edwprod/Partner_com/bin/exec_housekeep_main.ksh:# Called program : exec_housekeep.ksh
/home/edwprod/Partner_com/bin/exec_housekeep_main.ksh:#!/bin/ksh
/home/edwprod/Partner_com/bin/exec_housekeep_main.ksh: nohup ${SHELL_DIR}/exec_housekeep.ksh ${ROOT_ID} ${PLAN_ID} ${P_ACTION} ${LVL} &
/home/edwprod/Partner_com/bin/exec_housekeep_main.ksh
/home/edwprod/Partner_com/bin/retrive_pwd_edw.ksh:# echo " USAGE=> filename.ksh appl_schema"
/home/edwprod/Partner_com/bin/retrive_pwd_edw.ksh
/home/edwprod/Partner_com/bin/exec_pr_truncate_hkp.ksh:# Program Name : exec_pr_truncate_hkp.ksh
/home/edwprod/Partner_com/bin/exec_pr_truncate_hkp.ksh:# Calling program : exec_pr_truncate_hkp.ksh
/home/edwprod/Partner_com/bin/exec_pr_truncate_hkp.ksh
/home/edwprod/Partner_com/bin/exec_pr_post_housekeep.ksh:# Program Name : exec_pr_post_housekeep.ksh
/home/edwprod/Partner_com/bin/exec_pr_post_housekeep.ksh:# Calling program : exec_pr_post_housekeep.ksh
/home/edwprod/Partner_com/bin/exec_pr_post_housekeep.ksh
The command searches all the shell scripts and find the name of scripts being called inside. I need to exactly fetch the ksh name alone from this result. Please help on this?
You could give this a shot (-E is for extended regex, -o is for the output only matching pattern):
find `pwd` -type f -name "*.ksh" -exec grep -E -o "[a-zA-Z0-9_]*\.ksh" '{}' \;
To get a unique list:
find `pwd` -type f -name "*.ksh" -exec grep -E -o "[a-zA-Z0-9_]*\.ksh" '{}' \; |sort -u
Hope this helps!

Resources