Create a unique file name

I saw a lot of messages about creating a unique file name from naive% TIME% to believable (but insufficient)% RANDOM%. Using wmic os get localdatetime much better, but it can still fail on multiple CPUs / main machines. The following script will ultimately fail when run in 5+ shells on a multicore machine.

 @ECHO OFF SETLOCAL ENABLEDELAYEDEXPANSION FOR /L %%i IN (0, 1, 1000) DO ( FOR /F "usebackq" %%x IN (`wmic os get localdatetime ^| find "."`) do (set MYDATE=%%x) ECHO MYDATE is now !MYDATE! IF EXIST testuniq_!MYDATE!.txt ( ECHO FAILED ON !MYDATE! GOTO TheEnd ) COPY NUL >testuniq_!MYDATE!.txt ) :TheEnd EXIT /B 0 

Does anyone have a reliable way to create a unique file name in a shell script?

+11
windows cmd batch-file
source share
8 answers

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

There are other ways:

At first, I assume that you are trying to create a unique temporary file. Since the file will be deleted after the process is complete, all you need to do is set an exclusive lock on the resource, whose name is derived from your temp file name. (in fact, I'm going the other way around - the name temp comes from the name of the locked resource).

Reassignment sets an exclusive lock on the output file, so I just get the name from time to time (with 0.01 second preciscion) and try to lock the file with this name in the temporary user folder. If this fails, I will go back and try again until I succeed. As soon as I succeed, I am guaranteed to get ownership of this castle and all derivatives (if someone does not intentionally violate the system).

Once my process is complete, the lock will be released, and a temporary file with the same name can be reused later. But the script usually deletes the temporary file after completion.

 @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 

A loop due to name clashes should be rare if you really don't stress your system. If so, you can reduce the chance of looping by 10 times by using WMIC OS GET LOCALDATETIME instead of %TIME% .


If you are looking for a permanent unique name, the problem is a bit more complicated, since you cannot maintain an indefinite lock. In this case, I recommend using the LocalDateTime method for the WMIC system in combination with two name conflict checking.

The first check simply verifies that the file does not exist yet. But this is a condition of the race - two processes can do the check at the same time. The second check creates a file (empty) and sets a temporary exclusive lock on it. The trick is to make sure that the lock is maintained for a certain period of time, which is longer than another process, to check if the file exists. I am lazy, so I just use TIMEOUT to set 1 second to wait - this is more than necessary.

Procedure: getUniqueFile expects three arguments - the base name, the extension, and the name of the variable in which the result should be stored. The base name may contain disk and path information. Any information about the path must be valid, otherwise the program will enter an infinite loop. This problem can 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 ensure that a new unique file name is returned for the given path. There are many possibilities for optimization to set some command to be executed during the lock check, which takes a lot of time but not too long.

+10
source share

You can use certutil for base64 encode %date% %time% with seed %random% as follows:

 @echo off setlocal :: generate unique ID string >"%temp%\~%~n0.%username%.a" echo %username%%date%%time%%random% >NUL certutil -encode "%temp%\~%~n0.%username%.a" "%temp%\~%~n0.%username%.b" for /f "usebackq EOL=- delims==" %%I in ("%temp%\~%~n0.%username%.b") do set "unique_id=%%I" del "%temp%\~%~n0.%username%.a" "%temp%\~%~n0.%username%.b" echo %unique_id% 

In case the same script is run from the same directory by several users, I added %username% to temporary files to avoid further conflicts. I suppose you could replace %random% with %username% for the same effect. Then you will get a conflict only if one user executes the same code block twice.

(Edit: added %username% as a seed for uniqueness.)

+4
source share

The Batch-JScript script hybrid uses the WSH fso.GetTempName () method, which was designed specifically for this purpose:

 @if (@CodeSection == @Batch) @then @echo off for /F "delims=" %%a in ('cscript //nologo //E:JScript "%~F0"') do set "fileName=%%a" echo Created file: "%fileName%" goto :EOF @end var fso = new ActiveXObject("Scripting.FileSystemObject"), fileName; do { fileName = fso.GetTempName(); } while ( fso.FileExists(fileName) ); fso.CreateTextFile(fileName).Close(); WScript.Echo(fileName); 
+4
source share
 @echo off :: generate a tempfilename in %1 (default TEMPFILE) :loop set /ay$$=%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 out version of my tempfile generator for creating a file called tempnn.nnn in the %temp% directory.

Once tempnn.nnn been created, then just create as many additional tempfiles as you like for the process by adding the suffix to %y$$% , like %y$$%.a , etc. Of course, this assumes that some other process does not arbitrarily create file names without using this procedure.

+3
source share

I like the Aacini JScript hybrid solution. As long as we borrow from other runtimes, how about using the .NET System.GUID with PowerShell?

 @echo off setlocal for /f "delims=" %%I in ('powershell -command "[string][guid]::NewGuid()"') do ( set "unique_id=%%I" ) echo %unique_id% 
+2
source share

Once upon a time, in a galax..newsgroup called alt.msdos.batch, the participant insisted on publishing a QBASIC solution for every issue raised, wrapped in a batch wrapper and claiming it was batch because it used only standard Microsoft-supplied software.

After a long time, he was cured of his wandering ways, but since then I have since been severely allergic to hybrid methods. Of course, if it heals a problem, blah, blah, and sometimes it doesnโ€™t escape this course (for example, run in batches.) Besides, I really donโ€™t want the other to learn a new language or two ... So that's why I avoid solutions * script. YMMV.

So - here is 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 , not just %random% , so the search for โ€œx123xโ€ will not detect โ€œx1234xโ€.

Then create a detailed list of tasks by placing a line underlining the title and title. The first line returned by findstr will be underlined, the next will set the targetline , and any further returns indicate that the header is not unique, so go back, change it and try again until it becomes unique.

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

Result: this PID process, which should be unique and, therefore, can be used as the basis for the unique name tempfile, is to build it in a directory reserved for this purpose.

+1
source share

I would like to introduce another method and find out if there are any holes in this. Please let me know. Thank you

 C:>title my shell a80en333xyb C:>type getppid.ps1 (Get-WmiObject -Class Win32_Process -Filter "processid='$pid'").ParentProcessId C:>powershell getppid.ps1 2380 

Or, to run it without creating a .ps1 file:

 powershell -NoProfile -Command "(Get-WmiObject -Class Win32_Process -Filter "processid=''$pid''").ParentProcessId" 

To ensure that the correct task is found, an unlikely value is specified for the header, and the tasklist.exe file is used to find the value returned by getppid.ps1.

 C:>tasklist /v | find "2380" cmd.exe 2380 RDP-Tcp#0 7 8,124 K Running PHSNT\pwatson 0:00:03 my shell a80en333xyb 
+1
source share

To make the contents of the file an object, use the memaddress pointer as the first part of your file name and Rand () as your second part. the memory address will be unique for all objects, even when running multiple instances.

0
source share

All Articles