Reputation: 1484
I'm looking for a way for a batch file to move itself to a known location after execution. Self-moving - seemed to be the most apt name but I'm sure there's a technical term for it. I want to move the batch file after all the other code has run.
move "C:\temp\move_me.bat" "D:\temp\move_me.bat"
Where move_me.bat is the eponymous batch file placed in c:\temp to be move to d:\temp.
I get the error
The batch file cannot be found.
Even though the batch file moved location. So I'm a little confused. Do I need to supress the error or would it be best to copy the batch file and delete the source? Or is there a better way of doing this?
Upvotes: 3
Views: 2648
Reputation: 34969
The Windows command prompt cmd
does not cache batch files in memory, it reads them from the file line by line1. Therefore you receive an error as soon as the move
command has been finished, because the file cannot be found any more.
You can call the batch file like this to suppress the error:
"C:\temp\move_me.bat" 2> nul
But this supresses every other error messages also unintentionally.
Anyway, perhaps the following approach works for you -- this is the script C:\temp\move_me.bat
:
if /I "%~dp0"=="D:\temp\" exit /B
rem // (some other code here...)
copy "%~f0" "D:\temp\%~nx0"
"D:\temp\%~nx0" & del "%~f0"
At first, the location of the currently run batch file is checked against D:\temp\
; if it is equal, the batch file is immediately terminated.
Finally, the original batch file (accessed by %~f0
2) is copied (not moved) to the new location D:\temp
(the file name, %~nx0
, remains the same).
The next line runs the batch file from the new location, but without using call
, which is needed to return to the calling batch script, but this is not what we want. The &
operator lets the next command execute when the previous one is finished. Although call
is not used, the next command is still executed as the entire line has already been read and parsed by cmd
. But execution control is now at the new instance of the batch file, so the error message The batch file cannot be found.
does no longer appear.
The aforementioned if
query quits execution of the copy of the batch file immediately, so its other code does not run twice.
In case you do not want to skip the execution of the copied batch file, remove the if
command line and modify the copy
command line to get this:
rem // (some other code here...)
copy "%~f0" "D:\temp\%~nx0" > nul || exit /B
"D:\temp\%~nx0" & del "%~f0"
The > nul
portion suppresses display messages (including the summary 1 file(s) copied.
). The ||
operator executes the next command only in case the copying fails. So when the original batch file is executed, the copying is done as expected. When the copied batch file runs, copy
tries to copy the batch file onto itself, which results in the message The file cannot be copied onto itself.
(suppressed by > nul
) and in an error that fires the exit /B
command (due to ||
) to leave the batch file, so the last line is not attempted to be executed.
You can achieve the same behaviour also using move
; so the related code looks like this:
if /I "%~dp0"=="D:\temp\" exit /B
rem // (some other code here...)
move "%~f0" "D:\temp\%~nx0" & "D:\temp\%~nx0"
Or, if you want the other code not to be skipped for the moved script:
rem // (some other code here...)
if /I not "%~dp0"=="D:\temp\" move "%~f0" "D:\temp\%~nx0" & "D:\temp\%~nx0"
The if
query is necessary as move
, in contrast to copy
, does not return an error if source and destination are equal.
Here is a comprehensive solution for a batch file that moves itself and gives control to the moved one afterwards. Take a look at all the explanatory remarks to find out what code is run by what batch file instance:
@echo off
rem // Define constants here:
set "_TARGET=D:%~pnx0" & rem /* (this defines the movement destination;
rem in your situation, the original batch file is
rem `C:\temp\move_me.bat`, so the target file is
rem `D:\temp\move_me.bat` (only drive changes)) */
rem // (code that runs for both batch file instances...)
echo Batch file: "%~f0"
echo [executed by both files before the movement check]
rem // Check whether current batch file is the moved one:
if /I "%~f0"=="%_TARGET%" (
rem // (code that runs for the moved batch file instance only...)
echo Batch file: "%~f0"
echo [executed only by the moved file]
) else (
rem // (code than runs for the original batch file instance only...)
echo Batch file: "%~f0"
echo [executed only by the original file]
rem // Actually move the batch file here, then give control to the moved one:
> nul move "%~f0" "%_TARGET%"
"%_TARGET%"
rem /* (code that runs for the original batch file instance only;
rem this is run after the moved batch file has finished;
rem you must not use `goto` herein as the target label cannot be found,
rem because the original file does no longer exist at this point!) */
echo Batch file: "%~f0"
echo [executed only by the original file, but after the moved one has finished]
)
rem // (code that runs for the moved batch file instance only...)
echo Batch file: "%~f0"
echo [executed only by the moved file after the movement check]
exit /B
1) Note that parenthesised blocks of code (
/)
and continued lines ^
are considered as a single command line:
( echo This entire parenthesised block is echo considered as a single command line. ) echo This continued line & ^ echo as well.
2) Note that such argument references are immediately resolved as soon as a command line or block is read and parsed, hence before it is actually executed.
Upvotes: 4
Reputation: 91895
Batch files are executed line-by-line, rather than read into memory in one go. Do you have any more commands after the move
command?
However, batch files also have the feature that invoking another batch file will overlay your batch file.
So, you might be able to get away with:
if "%1" == "continuation" goto :continuation
REM interesting things go here
move "C:\Temp\move_me.bat" "D:\Temp\move_me.bat"
"D:\Temp\move_me.bat" continuation
:continuation
REM more interesting things go here
Alternatively copy your batch file to the destination and have the continuation delete the original copy.
Or have your batch file copy itself to the destination (and delete itself) as the first thing it does. That might be easier to understand.
Upvotes: 1