0

I'm attempting to standardize a portion of the copying in a script I was given.

Because of this it would be nice to re-write the following block:

REM Copy files
for %%x in (%var1%) do (
    copy %src%\%%x %dest%\%%x
)

where var1 contains strings in the format folder\folder\file.fileExtension, so an example would be:

var1=test\test1\test1.txt test\test1\test2.txt test\test3\randomfile.cmd

The destination doesn't exist yet (currently it's just created separately).

Unfortunately robocopy won't copy this way (It won't accept the format folder\file and folder\file as source and destination from what I've seen). This means I need to either break the variable at the last backslash into two variables or use a different copy tool (like copy or xcopy), but this is "discouraged".

Does anyone have a clever way of getting robocopy to accept this format without creating a new function to break the passed variable into two new variables? Or is there a different way to store an iterable list of folder\file paths that would remove this issue?

I've been able to retrieve the name portion from the passed parameter given %%~n but am unable to retrieve the path portion of the passed parameter as it's not a full path. I attempted to use a for loop to just cut that portion off (similar to results here: Last token in batch variable)

That doesn't work as it's a \ deliminated string, and as far as I know you can't cycle through in FOREACH style in for /f.

Community
  • 1
  • 1
Randy Cork
  • 45
  • 9
  • 1
    Type `for /?` -- you'll find modifiers that return the path components, like `%%~dx`, `%%~px`, `%%~nx`, `%%~xx`; – aschipfl Nov 02 '15 at 23:03
  • Hi. This is helpful information, and works great for separating the filename in the parameter. However, it doesn't appear to work (as far as I can tell) for the beginning of the parameter as it's not a full file path. For instance, the path would be test\test1\test2, which would be found under z:\notTest\notTest2. – Randy Cork Nov 03 '15 at 17:06

3 Answers3

1

This is the full solution I went with, which is very similar to what aschipfl posted (there are additional things I've modified from my actual script to make more sense - robocopy wrapper is a wrapper script that handles robocopy errors better for instance, and changing variable names):

....

REM Copy SWFs
set src=%SV1%\%V2%\%V3%
set dest=.\%DV1%\%V2%\%V3%
call :doPatchSWFFileCopy

set src=%SV1%\%V2%\%V3%
set dest=.\%DV1%\%DV2%\%V3%
call :doPatchSWFFileCopy

....

:doPatchSWFFileCopy
SETLOCAL EnableDelayedExpansion
for %%I in (%_filelist_%) do (
    REM Append file path to source and dest, as well as a pipe to prevent same name copies
    set "src=%src%\%%~I|"
    set "dest=%dest%\%%~I|"
    REM Truncate only LAST item (file name + extension) and the pipe character from the paths:
    set "src=!src:%%~nxI|=!"
    set "dest=!dest:%%~nxI|=!"
    REM truncate trailing slash
    call %robocopywrapper% "!src:~,-1!" "!dest:~,-1!" "%%~nxI"
)
ENDLOCAL
goto :EOF
Randy Cork
  • 45
  • 9
  • What does `%robocopywrapper%` contain? – aschipfl Nov 12 '15 at 20:45
  • It's a separate script that handles robocopy error values. It's essentially a robocopy call with passed parameters that returns "0" if the robocopy error level is in a range, because robocopy has 16 return values of which most are actually not errors. – Randy Cork Nov 12 '15 at 21:56
  • I see; perhaps it would be a good idea to include it here in order for your answer to be complete... – aschipfl Nov 12 '15 at 23:05
0

Considering your mentioned for block, you can replace copy with the robocopy command in the following way:

for %%v in (%listOfFiles%) do (robocopy %%~dv%%~pv %destinationPath% %%~nv%%~xv)

As already mentioned in the comments, %%~dv, %%~pv, %%~nv, %%~xv are statements which respectively expand the disk, the path (disk not included), the name and the extension of the %%v variable. Of course, this expansions are possible only with variables that point a path of a file. In the end, the syntax of the robocopy command requires at least a source path, a destination path and a list of files to copy. Then, if the destination path is previously defined, the script should work correctly.

Edit n.r 1
If in the %listOfFiles% file there are not full paths listed but only a portion of them, you can use the * character to try to fill it in the right way. A simple example of this functionality can be:

C:\Users\username>cd D*p
C:\Users\username\Desktop>_
rudicangiotti
  • 566
  • 8
  • 20
  • Unfortunately I am running into some issues as I can't use %%~pv as the passed parameter is not a full file path. If I attempt to use it I get the file path to the script, then the beginning portion of the parameter, where all I want is the beginning portion of the parameter. – Randy Cork Nov 03 '15 at 19:53
  • Hi. Regarding your edit, they aren't in the same path. I am attempting to run the script from one drive and the path from the passed parameter is in another drive. I will start testing some examples using * to ensure it doesn't work or if I am incorrect. – Randy Cork Nov 03 '15 at 20:05
  • @RandyCork do you mean that in the text file which contains all the file paths, there are not properly full paths? For instance, are there strings like `Folder\SubFolder\File.txt` while the file here mentioned is actually stored in the `C:\Users\username\Folder\SubFolder\File.txt` path? In this way, you are not able to properly use `robocopy` because it needs the full path, isn't it? – rudicangiotti Nov 03 '15 at 23:42
  • Correct the strings are partial paths, so I have to split them to add the beginning part to another variable for the full path (different vars for source and dest combined with this path), and the last part for the file name. Robocopy requires the format "source dest files". I believe aschipfl has a working solution below I am just running it through testing before I confirm. – Randy Cork Nov 04 '15 at 22:04
  • If the first part of the path is constant and you know it, you can set it as a variable and recall it inside the `for` cycle, as @aschipfl partially suggested in his example. – rudicangiotti Nov 04 '15 at 23:04
0

The following code could work for you (see rem remarks for brief explanations):

rem define source and destination roots here (no trailing `\` allowed!):
set "src=D:\path\to\source\dir"
set "dst=D:\path\to\destin\dir"
rem define list of items here:
set var1="test\test1\test1.txt" "test\test1\test2.txt" "test\test3\randomfile.cmd"

setlocal EnableDelayedExpansion
for %%I in (%var1%) do (
    rem append variable item to source and destination roots,
    rem and append a `|` which is not allowed in file names:
    set "src=%src%\%%~I|"
    set "dst=%dst%\%%~I|"
    rem truncate only LAST item (name+ext.) and `|` from the paths:
    set "src=!src:%%~nxI|=!"
    set "dst=!dst:%%~nxI|=!"
    rem supply paths with trailing `\` removed and LAST item (name+ext.) to `robocopy`:
    robocopy "!src:~,-1!" "!dst:~,-1!" "%%~nxI"
)
endlocal

As you might notice, there is a pipe symbol | appended temporarily, which is a forbidden character for file paths. This has been done that the approach does not fail even in case one or more directories in all the affected trees have got the same name as any of the copied files (test1.txt, test2.txt or randomfile.cmd in the example).

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • This is the solution I went with, but I had to modify it some as I was using it multiple times. I instead treated it like a function and used a call :DoCopy wrapper. – Randy Cork Nov 10 '15 at 17:01
  • Sure I will do that. I wasn't sure if that was appropriate or not. – Randy Cork Nov 12 '15 at 20:35