How to copy the two most recent log files to another folder?

不打扰是莪最后的温柔 提交于 2019-12-24 07:42:13

问题


I am trying to copy the two most recent error logs from a source location to another folder which is easier to access. I found the code below on Magoo's post here and the instructions were to replace echo %%i with the appropriate copy command. I am having a hard time with that for some reason.

@ECHO OFF
SETLOCAL
SET transfer=xx
FOR /f "delims=" %%i IN ('dir/b/a-d/o-d *.*') DO IF DEFINED transfer CALL SET transfer=%%transfer:~1%%&ECHO %%i

My final line with the echo %%i replaced looks like this:

SET transfer=%%transfer:~1%%& xcopy /y "C:\source_location" "D:\target_location"

回答1:


This batch file can be used for the task to copy just the two newest files in specified source directory to the specified target directory independent on which directory is the current directory on execution of the batch file.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileCount=xx"
set "SourcePath=C:\source_location"
set "TargetPath=D:\target_location"

set "SourcePath=%SourcePath:/=\%"
set "TargetPath=%TargetPath:/=\%"

if not "%SourcePath:~-1%" == "\" set "SourcePath=%SourcePath%\"
if not "%TargetPath:~-1%" == "\" set "TargetPath=%TargetPath%\"

for /F "eol=| delims=" %%I in ('dir "%SourcePath%" /A-D /B /O-D 2^>nul') do (
    %SystemRoot%\System32\xcopy.exe "%SourcePath%%%I" "%TargetPath%" /C /I /Q /H /R /Y >nul
    call set "FileCount=%%FileCount:~1%%"
    if not defined FileCount goto FileCopyDone
)

:FileCopyDone
rem Other commands can be inserted here.
endlocal

The batch file first sets up a local environment with enabled command extensions as needed here and with delayed environment variable expansion disabled to be able to copy also files of which full qualified file name (drive + path + name + extension) contain one or more exclamation marks. Please read this answer for details about the commands SETLOCAL and ENDLOCAL and what happens in background on using these two commands.

The number of files to copy is determined by the number of x characters of the string assigned to environment variable FileCount. xx means copying two files and xxxx would be for copying four files. It does not really matter which character is used in the string assigned to environment variable FileCount, the length of the string matters which must be at least one character.

Then the batch file makes sure that \ is used in source and target path because this is the directory separator on Windows and not / as it is on Linux and Mac.

Next source and target path are defined in batch file. These two environment variables could be also defined dynamically instead of fixed by getting assigned the first and second argument passed to the batch file to these two environment variables.

The batch file is written for source path always ending with Windows directory separator \ and for that reason the batch file makes sure that the last character of source path is really a backslash.

The target path must end with a backslash. That is very important on using it as target string for command XCOPY as explained very detailed in my answer on batch file asks for file or folder. For that reason the batch file makes sure that target path also ends with a backslash.

The command FOR with option /F starts a new command process with %ComSpec% /c and the command line specified between ' as further arguments in background. So executed by FOR is with usual Windows installation path:

C:\Windows\System32\cmd.exe /c dir "C:\source_location\" /A-D /B /O-D 2>nul

DIR executed by background command process searches with the specified arguments

  • in the specified source directory
  • for files because of option /A-D (attribute not directory)
  • matching the default wildcard pattern * (all)

and outputs

  • in bare format because of option /B just the file names without path never enclosed in "
  • ordered reverse by last modification date because of option /O-D and not using option /TC (creation date) or /TA (last access date) which means first the newest modified file and last the oldest modified file.

The output by DIR is written to handle STDOUT of the started background command process.

2>nul redirects the error message output by DIR on not finding any file in specified directory from handle STDERR to device NUL to suppress this error message.

Read the Microsoft article about Using Command Redirection Operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line in a separate command process started in background.

FOR captures everything written by DIR to handle STDOUT of started command process and processes this output line by line after started cmd.exe terminated itself.

FOR ignores empty lines which do not occur here because of DIR outputs the list of file names without empty lines because of using /B.

FOR would split up by default a line into substrings (tokens) using normal space and horizontal tab character as delimiters. After this substring splitting is done FOR would by default check if the first substring starts with default end of line character ; in which case the line would be ignored like an empty line. Otherwise FOR would assign first space/tab delimited string to the specified loop variable I and would execute the command lines in the command block between ( and matching ).

A file name could be for example ;Test File!.log, i.e. a file name starting with a space and a semicolon and containing one more space and an exclamation mark. Such a file name would be split up to ;Test (without space at beginning) and File!.log and next ignored by FOR because of ;Test starts with a semicolon.

For that reason the end of line character is redefined from default semicolon to a vertical bar with eol=| which is a character no file or folder name can contain according to Microsoft documentation about Naming Files, Paths, and Namespaces. And line splitting behavior is disabled with delims= at end of options argument string after for /F which defines an empty list of delimiters. So the file name as output by DIR is assigned to loop variable I without any modification even on being a very unusual name for a file.

The file of which name and extension and without path is assigned to loop variable I is copied with command XCOPY to the specified target directory with keeping its name and extension.

XCOPY is used here instead of COPY for following reasons:

  1. XCOPY creates the entire directory path to target directory if not already existing.
    COPY never creates the directory structure to target directory.
  2. XCOPY overwrites with the used parameters even an already existing file in target directory having set the read-only file attribute. COPY overwrites never a read-only file.

The success or error of the file copying process is not evaluated by the batch file although that would be also possible with an additional command line like if errorlevel 1 ....

The next line is a bit tricky to understand for beginners in batch file writing.

Windows command processor cmd.exe parses the entire command block starting with ( up to matching ) and replaces in this command block all occurrences of %variable% environment variable references by the current values of the referenced environment variables before the command FOR is executed make use of this command block. This behavior is not good in case of modifying the value of an environment variable within such a command block and evaluating the modified environment variable value in same command block as done here on value xx of environment variable FileCount.

See also How does the Windows Command Interpreter (CMD.EXE) parse scripts?

The standard solution is using delayed expansion as explained by help of command SET on an IF and a FOR example output on running in a command prompt window set /?. But this would result here in interpreting all exclamation marks in file name assigned to loop variable I as begin/end of a delayed expanded environment variable reference and not as literal character of the file name. So the FOR loop would not work as expected just because of ! in file names or directory paths.

Another solution is using command CALL to SET an environment variable and reference the environment variable value with two percent signs on each side instead of just one. The command line

call set "FileCount=%%FileCount:~1%%"

is modified on parsing the entire command block before running FOR to

call set "FileCount=%FileCount:~1%"

The command CALL results during each iteration of the loop in parsing the command line a second time by cmd.exe and so on first (newest) file the command SET is executed with "FileCount=x" as argument string as there is just one x after first character of current value string and on second file with "FileCount=" as there is now no more character after first x which undefines the environment variable FileCount.

So after second file was copied the environment variable FileCount is not defined anymore which results in IF condition is true and so command GOTO is executed by Windows command processor to continue execution of the batch file not anymore with the FOR loop, but on the line below the line with label FileCopyDone. So the FOR loop is exited after copying second newest file to specified target directory.

Here is the solution using delayed expansion working only if the two directory paths and all files to copy do not contain an exclamation mark.

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set FileCount=2
set "SourcePath=C:\source_location"
set "TargetPath=D:\target_location"

set "SourcePath=%SourcePath:/=\%"
set "TargetPath=%TargetPath:/=\%"

if not "%SourcePath:~-1%" == "\" set "SourcePath=%SourcePath%\"
if not "%TargetPath:~-1%" == "\" set "TargetPath=%TargetPath%\"

for /F "eol=| delims=" %%I in ('dir "%SourcePath%" /A-D /B /O-D 2^>nul') do (
    %SystemRoot%\System32\xcopy.exe "%SourcePath%%%I" "%TargetPath%" /C /I /Q /H /R /Y >nul
    set /A FileCount-=1
    if !FileCount! == 0 goto FileCopyDone
)

:FileCopyDone
rem Other commands can be inserted here.
endlocal

There is one more solution also without using delayed expansion which I saw on this answer written by Compo.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileCount=2"
set "SourcePath=C:\source_location"
set "TargetPath=D:\target_location"

set "SourcePath=%SourcePath:/=\%"
set "TargetPath=%TargetPath:/=\%"

if not "%SourcePath:~-1%" == "\" set "SourcePath=%SourcePath%\"
if not "%TargetPath:~-1%" == "\" set "TargetPath=%TargetPath%\"

for /F "tokens=1* delims=:" %%H in ('dir "%SourcePath%" /A-D /B /O-D 2^>nul ^| %SystemRoot%\System32\findstr.exe /N "^"') do (
    %SystemRoot%\System32\xcopy.exe "%SourcePath%%%I" "%TargetPath%" /C /I /Q /H /R /Y >nul
    if %FileCount% == %%H goto FileCopyDone
)

:FileCopyDone
rem Other commands can be inserted here.
endlocal

The output of DIR is redirected to FINDSTR which outputs all lines unfiltered because of the regular expression search string with just ^ results in a positive match on all lines. But the file names are output with an incremented (line) number and a colon at beginning because of option /N.

So an output by DIR like

Newest File.log
Other File.log
Oldest File.log

is modified by FINDSTR to

1:Newest File.log
2:Other File.log
3:Oldest File.log

The command FOR with the options tokens=1* delims=: splits up each line into the line/file number left to the colon assigned to loop variable H and the file name right to colon assigned to next loop variable I according to ASCII table.

The file is copied and next a case-sensitive string comparison is done to check if the number of the file is equal the string value assigned to environment variable FileCount. On equal number strings the loop is exited with command GOTO because of the defined number of newest files are copied already to the target.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • findstr /?
  • goto /?
  • if /?
  • rem /?
  • set /?
  • setlocal /?
  • xcopy /?


来源:https://stackoverflow.com/questions/56810601/how-to-copy-the-two-most-recent-log-files-to-another-folder

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!