set -x # to output to screen the execution of the script
while read f d
do
if [[ -n $d ]] && [[ -n $f ]]
then
filepath=$(locate $f)
if [[ -e $filepath ]]
then
mkdir -p "/home/data/$d" && cp "$filepath" "/home/data/$d"
fi
fi
done < data.txt
Please, post the script that you are running.
Please, post the output from your screen of running the script.
I presume you set -x in it, in fact make that line set -xv to be even more verbose and post the result to help troubleshoot.
That shows the risk of using the command locate for what you want. It is returning multiple paths where the filename matches (concatenated as an string with newlines) and since, on purpose, I am not accepting the return as valid, I am checking if that result is an actual existent path, only [[ -e /home/vv/lada4 ]] is real.
You can always loop through the result of locate if you want to accept its result as "duplication" but if it has the same filename you'll be just overwriting them at destination. But I suspect that would not be the case, neither, since they could be a partial match or even not a file:
/home/files/file1
/var/lib/mysql/ib_logfile1
/home/bbb/11/22/file2
/home/data/thisforfile2
/home/data/thisfolderforfile3
/home/ttt/file3
The point is that you do not have any guarantee that what locate is giving is what you expect.
Your statement / request is not quite clear. You use locate yourself in post#1. Do you want all files locate d (including the ones whose file names are supersets of the search term) to be copied to your target directory given in your data file? If several files with identical file names exist, they will overwrite each other - which one should survive?
To give you a starting point, you might want to consider / analyse this:
while read FN DN
do [ -d $DN ] || echo mkdir $DN
for LN in $(locate $FN)
do [ ! $FN = ${LN##*/} ] && continue
echo cp $LN $DN
done
done < datafile
It will check the resp. file name against the one in datafile and copy only if identical, but will not check for overwriting. Directories are checked for existence and created if non-existent. It will of course depend on the locate-DB to be up to date, and on those names not containing white space as these would confuse the for loop.
Give it a try an comment back
Not truly hard. The hard part is for you to recognize the way that you discern what files need to be copied when you do it manually and communicate it in a way that can be translated into an automation script without getting unexpected results. I hope I have clearly pointed out that accepting the result from locate is not it.
For example the snippet below it might be alright if you understand that locate MUST never return a matched directory, a partial match in filename or directory name and never returns neither with spaces on them. Otherwise, you MUST accommodate for those conditions.
Would this work?
homepath="/home/data"
while read f d; do
if [[ -n $f ]] && [[ -n $d ]]; then
paths=$(locate $f)
while read -r p; do
if [[ -f $p ]] && [[ ! -f ${homepath}/${d}/${p##*/} ]]; then
mkdir -p "${homepath}/${d}" && cp "$p" "${homepath}/${d}"
fi
done <<< "$paths"
fi
done < data.txt
Instead of locate i change it to "find /specific_directory -name"
Hi Rudi, care to share why it worked? Do you mind explaning it line by line please? Also why While? WHy not another for loop?
THanks
---------- Post updated at 12:21 PM ---------- Previous update was at 12:18 PM ----------
THis code belongs confuse me
while read f d
is the same with
awk '{print $1, $2}'
---------- Post updated at 12:25 PM ---------- Previous update was at 12:21 PM ----------
Please explain why,
while read f d
will correspond to column 1 and column 2 in a file. If you can explain that to me. I will not rely on for loop anymore. ill start writing script with while.. i can do if statement and for loop..simple scripting for linux admin...but that while is really help ful,but need to understand why
A for loop is _almost_ never a good choice to iterate over lines in a file, nor for dealing with file/directory paths since the default separator is white space. That means that if a filename or path contains spaces it might give you an undesirable result.
The read built-in command is good at reading lines and better at tokenization of the lines.
If you give it one variable, it will read the whole line into it.
If you give it two variables it will split the line into two substrings at the first space
If you give it three variables it will split the line into three substrings at the first and second space.
And so on. read doesn't loop, it reads once. Thus the while loop to help read to imitate an iteration of all lines.
Summary:
Iterate over every line in datafile, splinting it into two tokens: f (filename) d (directoryname). The f and d could be any place holder names.
while read f d; do ... done < datafile
---------- Post updated at 10:14 AM ---------- Previous update was at 09:54 AM ----------
It would require that you know a bit about how AWK works. In this example each record is indexed into fields using white spaces as field separator and it is asking it to just output the values of fields one and two.
Little to add to Aia's excellent explanation. man bash and man awk help, as almost always. My proposal was a "proof of concept", not a full blown solution to your (not quite understood) problem.
while read FN DN # read TWO fields from stdin (here redirected from datafile: file name - directory name)
do [ -d $DN ] || echo mkdir $DN # test for existence of target directory; create if missing (remove echo)
for LN in $(locate $FN) # loop through matching names from locate-DB
do [ ! $FN = ${LN##*/} ] && continue # test for non-exact match; skip cp if not identical
echo cp $LN $DN # copy located name to target directory (echo for testing purposes; remove if OK)
done # end of for loop
done < datafile # end of while loop incl. redirection from datafile
Not sure how you want the script to react if locate identifies more that one exact match for a file. RudiC's current script will silently overwrite the file with each subsequent identified match.
If you would like to keep the first match found and then move on to the next file replace:
echo cp $LN $DN # copy located name to target directory (echo for testing purposes; remove if OK)
with
echo cp $LN $DN && break # copy located name to target directory (echo for testing purposes; remove if OK) and move onto next file
You could also detect the file was already there and report some sort of warning like Warning: Not replacing existing file xxx with /path/to/xxx