Christian Bongiorno
Christian Bongiorno

Reputation: 5648

for loop and files with spaces

Here is my command

for i in `find . -name '*Source*.dat'`; do cp "$i" $INBOUND/$RANDOM.dat; done;

Here are the files (just a sample):

/(12)SA1 (Admitting Diagnosis) --_TA1-1 + TA1-2/Source.dat
./(12)SA1 (Admitting Diagnosis) --_TA1-1 + TA1-2/Source_2000C.dat
./(13)SE1 (External Cause of Injury) --_ TE1-1+TE1-2/Source.dat
./(13)SE1 (External Cause of Injury) --_ TE1-1+TE1-2/Source_2000C.dat
./(13)SE1 (External Cause of Injury) --_ TE1-1+TE1-2/Source_POATest.dat
./(14)SP1(Primary)--_ TP1-1 + TP1-2/Source.dat
./(14)SP1(Primary)--_ TP1-1 + TP1-2/Source_2000C.dat
./(14)SP1(Primary)--_ TP1-1 + TP1-2/Source_ProcDateTest.dat
./(15)SP1(Primary)--_ TP1-1 + TP1-2 - SP2 -- TP2-1 + TP2-2/Source.dat
./(16)SP1(Primary)--_ TP1-1 + TP1-2 +TP1-3- SP2 -- TP2-1 + TP2-2/Source.dat
./(17)SP1(Primary)--_ TP1-1 + TP1-2 +TP1-3/Source.dat
./(18)SP1(Primary)--_ TP1-1 + TP1-2 - SP2 -- TP2-1 + TP2-2 - Copy/Source.dat
./(19)SD1 (Primary)+SD2 (Other Diagnosis)--_ TD12/Source.dat
./(19)SD1 (Primary)+SD2 (Other Diagnosis)--_ TD12/Source_2000C.dat
./(19)SD1 (Primary)+SD2 (Other Diagnosis)--_ TD12/Source_POATest.dat
./(2)SD3--_TD4 SD4--_TD4/Source.dat
./(2)SD3--_TD4 SD4--_TD4/Source2.dat

Those spaces are getting tokenized by bash and this doesn't work. In addition, I want to append some randomness to the end of these files so they don't collide in the destination directory but that's another story.

Upvotes: 1

Views: 343

Answers (5)

Lindsay Winkler
Lindsay Winkler

Reputation: 771

How about:

find . -name '*file*' -print0 | xargs -0 -I {} cp {} $INBOUND/{}-$RANDOM.dat

xargs is a handy way of constructing an argument list and passing it to a command.

  • find -print0 and xargs -0 go together, and are basically an agreement between the two commands about how to terminate arguments. In this case, it means the space won't be interpreted as the end of an argument.

  • -I {} sets up the {} as an argument placeholder for xargs.

As for randomising the file name to avoid a collision, there are obviously lots of things you could do to generate a random string to attach. The most important part, though, is that you verify that your new file name also does not exist. You might use a loop something like this to attempt that:

$RANDOM=$(date | md5)
filename=$INBOUND/$RANDOM.dat
while [ -e $filename ]; do
  $RANDOM=$(date | md5)
  filename=$INBOUND/$RANDOM.dat
done

I'm not necessarily advocating for or against generating a random filename with a hash of the current time: the main point is that you want to check for existence of that file first, just in case.

Upvotes: 1

rici
rici

Reputation: 241901

If all the files are at the same directory level, as in your example, you don't need find. For example,

 for i in */*Source*.dat; do
   cp "$i" $INBOUND/$RANDOM.dat
 done

will tokenize correctly and will find the correct files provided they are all in directories which are children of the current directory.

As @chepner points out in a comment, if you have bash v4 you can use **:

 for i in **/*Source*.dat; do
   cp "$i" $INBOUND/$RANDOM.dat
 done

which should find exactly the same files as find would, without the tokenizing issue.

Upvotes: 1

John Kugelman
John Kugelman

Reputation: 362037

find . -name '*Source*.dat' -exec sh -c 'cp "$1" "$2/$RANDOM.dat"' -- {} "$INBOUND" \;

Using -exec to execute commands is whitespace safe. Using sh to execute cp is necessary to get a different $RANDOM for each copy.

Upvotes: 3

michael501
michael501

Reputation: 1482

try something like

while read  i;do
  echo "file is $i"
  cp "$i" $INBOUND/$RANDOM.dat

done < <(find . -name '*Source*.dat') 

Upvotes: 0

Diego Sevilla
Diego Sevilla

Reputation: 29021

There are several ways of treating files with spaces. You can use findin a pipe, while and read:

find . -name '*Source*.dat' | while read file ; do cp "$file" "$INBOUND/$RANDOM.dat"; done

Upvotes: 0

Related Questions