Mr Mystery Guest
Mr Mystery Guest

Reputation: 1484

Self-moving batch file

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

Answers (2)

aschipfl
aschipfl

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 %~f02) 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

Roger Lipscombe
Roger Lipscombe

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

Related Questions