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 much better, but it can still fail on multiple CPU/core machines. The following script will eventually fail when run in 5+ shells on a multple core 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?
I would like to submit another method and find out if there are any holes is it. Please let me know. Thanks.
Or, to run it without creating a .ps1 file:
To verify that the correct task is found, the title is set to an unlikely value and tasklist.exe is used to search for the value returned by getppid.ps1.
I like Aacini's JScript hybrid solution. As long as we're borrowing from other runtime environments, how about using .NET's System.GUID with PowerShell?
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...
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 settargetline
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.
You could use
certutil
to base64 encode%date% %time%
with a%random%
seed like this:In case the same script is being run from the same directory by multiple users, I added
%username%
to the temp files to avoid further conflict. I suppose you could replace%random%
with%username%
for the same effect. Then you'd only get a conflict if a single user executes the same code block twice concurrently.(Edit: added
%username%
as a seed for uniqueness.)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.
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.
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"
The Batch-JScript hybrid script below uses WSH's fso.GetTempName() method that was designed precisely for this purpose: