Create unique file name Windows batch

前端 未结 10 1446
闹比i
闹比i 2020-12-16 15:57

I have seen many posts about creating a unique filename from the naive %TIME% to the plausible (but insufficient) %RANDOM%. Using wmic os get localdatetime is m

相关标签:
10条回答
  • 2020-12-16 16:24

    Any system that relies on a random number can theoretically fail, even one using a GUID. But the world seems to have accepted that GUIDs are good enough. I've seen code posted somewhere that uses CSCRIPT JScript to generate a GUID.

    There are other ways:

    First I will assume that you are trying to create a unique temporary file. Since the file will be deleted once your process ends, all you must do is establish an exclusive lock on a resource whose name is a derivative of your temp file name. (actually I go in reverse - the temp name is derived from the locked resource name).

    Redirection establishes an exclusive lock on the output file, so I simply derive a name from the time (with 0.01 second preciscion), and attempt to lock a file with that name in the user's temp folder. If that fails than I loop back and try again until I succeed. Once I have success, I am guaranteed to have sole ownership of that lock, and all derivitives (unless someone intentionally breaks the system).

    Once my process terminates, the lock will be released, and a temp file with the same name could be reused later on. But the script normally deletes the temp file upon termination.

    @echo off
    setlocal
    
    :getTemp
    set "lockFile=%temp%\%~nx0_%time::=.%.lock"
    set "tempFile=%lockFile%.temp"
    9>&2 2>nul (2>&9 8>"%lockFile%" call :start %*) || goto :getTemp
    
    :: Cleanup
    2>nul del "%lockFile%" "%tempFile%"
    exit /b
    
    
    :start
    :: Your code that needs the temp file goes here. This routine is called with the
    :: original parameters that were passed to the script. I'll simply write some
    :: data to the temp file and then TYPE the result.
    >"%tempFile%" echo The unique tempfile for this process is "%tempfile%"
    >>"%tempFile%" echo(%*
    type "%tempFile%"
    exit /b
    

    Looping due to name collision should be rare unless you are really stressing your system. If so, you can reduce the chance of looping by a factor of 10 if you use WMIC OS GET LOCALDATETIME instead of %TIME%.


    If you are looking for a persistent unique name, then the problem is a bit more difficult, since you cannot maintain the lock indefinitely. For this case I recommend the WMIC OS LocalDateTime approach, coupled with two checks for name collision.

    The first check simply verifies the file does not already exist. But this is a race condition - two processes could make the check at the same time. The second check creates the file (empty) and establishes a temporary exclusive lock on it. The trick is to make sure that the lock is maintained for a period of time that is longer than it takes for another process to check if the file exists. I'm lazy, so I simply use TIMEOUT to establish a 1 second wait - way more than should be necessary.

    The :getUniqueFile routine expects three arguments - a base name, an extension, and the variable name where the result is to be stored. The base name can include drive and path information. Any path information must be valid, otherwise the routine will enter an infinite loop. That issue could be fixed.

    @echo off
    setlocal
    
    :: Example usage
    call :getUniqueFile "d:\test\myFile" ".txt" myFile
    echo myFile="%myFile%"
    exit /b
    
    :getUniqueFile  baseName  extension  rtnVar
    setlocal
    :getUniqueFileLoop
    for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2"
    if exist "%rtn%" (
      goto :getUniqueFileLoop
    ) else (
      2>nul >nul (9>"%rtn%" timeout /nobreak 1) || goto :getUniqueFileLoop
    )
    endlocal & set "%~3=%rtn%"
    exit /b
    

    The above should be guaranteed to return a new unique file name for the given path. There is a lot of room for optimization to establish some command to execute during the lock check that takes "long enough" but not "too long"

    0 讨论(0)
  • 2020-12-16 16:24

    A long time ago in a galax..newsgroup called alt.msdos.batch, a contributor insisted on posting a QBASIC solution to each issue raised, wrapped in a batch shell and claiming it was batch because it only used standard Microsoft-supplied software.

    After a long time, he was cured of his errant ways, but I've been severely allergic to hybrid methods ever since. Sure - if it cures the problem, blah,blah - and sometimes there's no escaping that course (run batch silently, for instance.) Apart from that, I really don't want thebother of learning a new language or two...So that's why I eschew *script solutions. YMMV.

    So - here's another approach...

    @ECHO OFF
    SETLOCAL
    :rndtitle
    SET "progtitle=My Batch File x%random%x"
    TITLE "%progtitle%"
    SET "underline="
    SET "targetline="
    FOR /f "delims=" %%a IN ('TASKLIST /v^|findstr /i /L /c:"======" /c:"%progtitle%"') DO (
     IF DEFINED underline (
      IF DEFINED targetline GOTO rndtitle
      SET "targetline=%%a"
     ) ELSE (SET "underline=%%a")
    )
    
    :: Here be a trap. The first column expands to fit the longest image name...
    :pidloop
    IF "%underline:~0,1%"=="=" SET "underline=%underline:~1%"&SET "targetline=%targetline:~1%"&GOTO pidloop
    FOR /f %%a IN ("%targetline%") DO SET /a pid=%%a
    ECHO %pid%
    
    GOTO :EOF
    

    Essentially, set the title to something random. Note that x%random%x is used, not simply %random% so that a search for "x123x" won't detect "x1234x".

    Next, get a verbose tasklist, locating the line underlining the heading and the title. The first line returned by findstr will be the underline, the next will set targetline and any further returns indicate that the title is not unique, so go back, change it and try again until it is unique.

    Finally, get the process ID which is in the second column, noting that the first is of unknown width (hence why the underline is grabbed.)

    Result : this process's PID which must be unique and hence can be used as the basis for a unique tempfile name - build it in a directory reserved for the purpose.

    0 讨论(0)
  • 2020-12-16 16:37

    At first some remarks for using a PID as a part of a unique file name:

    When you just look for a parent PID this might be ambiguous because when you run that code in a for loop or in a call within your batch file always a new cmd.exe is created and you get the PID of this temporary cmd.exe process.

    So you should rather use the PID of your batch "root" cmd.exe process. In order to get that - instead of searching by title in the the task list, which is slow and might also be ambiguous, there is another approach.

    You can get the PID of your batch "root" cmd.exe process by using Windows API functions:

    • GetConsoleWindow provides the console window handle
    • GetWindowThreadProcessId provides the PID by using this console window handle

    You can run these Windows API functions by using PowerShell:

    Add-Type -MemberDefinition @"
      // HWND WINAPI GetConsoleWindow(void)
      [DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow")]
      public static extern IntPtr GetConsoleWindow();
    
      // DWORD GetWindowThreadProcessId(HWND hWnd, LPDWORD lpdwProcessId)
      [DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId")]
      public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
    
      public static uint GetPID()
      {
        IntPtr h = GetConsoleWindow();
        if ((uint)h==0) return 0;
        uint rc = 0;
        GetWindowThreadProcessId(h, out rc);
        return rc;
      }
    "@ -Name Win32 -NameSpace System
    
    [System.Win32]::GetPID();
    

    Within your batch file you can run the PowerShell code as an encoded command:

    set MyPID=
    for /f %%a in ('powershell -NoLogo -NoProfile -NonInteractive -EncodedCommand QQBkAGQALQBUAHkAcABlACAALQBNAGUAbQBiAGUAcgBEAGUAZgBpAG4AaQB0AGkAbwBuACAAQAAiAA0ACgBbAEQAbABsAEkAbQBwAG8AcgB0ACgAIgBrAGUAcgBuAGUAbAAzADIALgBkAGwAbAAiACwAIABFAG4AdAByAHkAUABvAGkAbgB0ACAAPQAgACIARwBlAHQAQwBvAG4AcwBvAGwAZQBXAGkAbgBkAG8AdwAiACkAXQANAAoAcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAgAGUAeAB0AGUAcgBuACAASQBuAHQAUAB0AHIAIABHAGUAdABDAG8AbgBzAG8AbABlAFcAaQBuAGQAbwB3ACgAKQA7AA0ACgBbAEQAbABsAEkAbQBwAG8AcgB0ACgAIgB1AHMAZQByADMAMgAuAGQAbABsACIALAAgAEUAbgB0AHIAeQBQAG8AaQBuAHQAIAA9ACAAIgBHAGUAdABXAGkAbgBkAG8AdwBUAGgAcgBlAGEAZABQAHIAbwBjAGUAcwBzAEkAZAAiACkAXQANAAoAcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAgAGUAeAB0AGUAcgBuACAAdQBpAG4AdAAgAEcAZQB0AFcAaQBuAGQAbwB3AFQAaAByAGUAYQBkAFAAcgBvAGMAZQBzAHMASQBkACgASQBuAHQAUAB0AHIAIABoAFcAbgBkACwAIABvAHUAdAAgAHUAaQBuAHQAIABsAHAAZAB3AFAAcgBvAGMAZQBzAHMASQBkACkAOwANAAoAcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAgAHUAaQBuAHQAIABHAGUAdABQAEkARAAoACkADQAKAHsADQAKACAASQBuAHQAUAB0AHIAIABoACAAPQAgAEcAZQB0AEMAbwBuAHMAbwBsAGUAVwBpAG4AZABvAHcAKAApADsADQAKACAAaQBmACAAKAAoAHUAaQBuAHQAKQBoAD0APQAwACkAIAByAGUAdAB1AHIAbgAgADAAOwANAAoAIAB1AGkAbgB0ACAAcgBjACAAPQAgADAAOwANAAoAIABHAGUAdABXAGkAbgBkAG8AdwBUAGgAcgBlAGEAZABQAHIAbwBjAGUAcwBzAEkAZAAoAGgALAAgAG8AdQB0ACAAcgBjACkAOwANAAoAIAByAGUAdAB1AHIAbgAgAHIAYwA7AA0ACgB9AA0ACgAiAEAAIAAtAE4AYQBtAGUAIABXAGkAbgAzADIAIAAtAE4AYQBtAGUAUwBwAGEAYwBlACAAUwB5AHMAdABlAG0ADQAKAFsAUwB5AHMAdABlAG0ALgBXAGkAbgAzADIAXQA6ADoARwBlAHQAUABJAEQAKAApADsADQAKAA^=^= 2^>nul') do (
      set "MyPID=%%~a"
    )
    
    if defined MyPID echo %MyPID%
    
    0 讨论(0)
  • 2020-12-16 16:38
    @echo off
    :: generate a tempfilename in %1 (default TEMPFILE)
    :loop
    set /a y$$=%random%+100000
    set y$$=temp%y$$:~1,2%.%y$$:~-3%
    if exist "%temp%\%y$$%" goto loop
    SET "y$$=%temp%\%y$$%"&copy nul "%temp%\%y$$%" >nul 2>NUL
    :: y$$ now has full tempfile name
    if "%1"=="" (set "tempfile=%y$$%") else (set "%1=%y$$%")
    

    Here's a cut-down version of my tempfile generator to create a file named tempnn.nnn in the %temp% directory.

    Once tempnn.nnn has been created, then it's simple to create as many further tempfiles as you like for the process by appending a suffix to %y$$%, eg %y$$%.a etc. Of course, that presumes that some other process doesn't randomly create filenames without using this procedure.

    0 讨论(0)
提交回复
热议问题