创建唯一的文件名Windows批处理

时间:2015-01-06 16:00:31

标签: windows batch-file cmd

我见过很多关于创建一个独特文件名的文章,从天真的%TIME%到合理的(但不足)%RANDOM%。使用wmic os get localdatetime要好得多,但它仍然可以在多台CPU /核心机器上失败。在多核计算机上运行5+ shell时,以下脚本最终会失败。

@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

有没有人有可靠的方法在shell脚本中创建唯一的文件名?

10 个答案:

答案 0 :(得分:10)

任何依赖于随机数的系统理论上都会失败,即使是使用GUID的系统也是如此。但世界似乎已经接受了GUID足够好。我发现在某处使用CSCRIPT JScript生成GUID的代码。

还有其他方法:

首先,我假设您正在尝试创建一个唯一的临时文件。由于文件将在您的流程结束后被删除,您所要做的就是在资源上建立一个独占锁,该资源的名称是临时文件名的衍生物。 (实际上我反过来 - 临时名称来自锁定的资源名称)。

重定向在输出文件上建立了一个独占锁,所以我只是从时间推导出一个名称(精确度为0.01秒),并尝试在用户的临时文件夹中锁定具有该名称的文件。如果失败了,我会回来再试一次,直到我成功为止。一旦我获得成功,我保证拥有该锁的唯一所有权以及所有衍生物(除非有人故意破坏系统)。

一旦我的进程终止,锁将被释放,并且稍后可以重用具有相同名称的临时文件。但是脚本通常会在终止时删除临时文件。

@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

由于名称冲突导致的循环应该很少,除非您真的在强调系统。如果是这样,如果您使用WMIC OS GET LOCALDATETIME而不是%TIME%,则可以将循环的几率降低10倍。


如果您正在寻找持久的唯一名称,那么问题就更难了,因为您无法无限期地维护锁定。对于这种情况,我建议使用WMIC OS LocalDateTime方法,并进行两次名称冲突检查。

第一项检查只是验证文件是否尚不存在。但这是竞争条件 - 两个进程可以同时进行检查。第二次检查创建文件(空)并在其上建立临时排他锁。诀窍是确保锁定的维护时间比另一个进程检查文件是否存在所需的时间长。我很懒,所以我只是使用TIMEOUT建立一个等待时间超过1秒的方式。

:getUniqueFile例程需要三个参数 - 基本名称,扩展名和要存储结果的变量名称。基本名称可以包括驱动器和路径信息。任何路径信息都必须有效,否则例程将进入无限循环。这个问题可以解决。

@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

应保证上面的内容为给定路径返回一个新的唯一文件名。在锁定检查期间建立一些执行命令的优化空间很大,这需要足够长的时间"但不是"太长"

答案 1 :(得分:4)

您可以使用certutil对带有%date% %time%种子的{64} %random%进行base64编码:

@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%

如果多个用户从同一目录运行相同的脚本,我将%username%添加到临时文件以避免进一步冲突。我想您可以用%random%替换%username%以获得相同的效果。然后,如果单个用户同时执行两次相同的代码块,则只会发生冲突。

(编辑:添加%username%作为唯一性的种子。)

答案 2 :(得分:4)

下面的Batch-JScript混合脚本使用专为此目的而设计的WSH的fso.GetTempName()方法:

@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);

答案 3 :(得分:3)

@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$$%")

这是我的临时文件生成器的简化版本,用于在tempnn.nnn目录中创建名为%temp%的文件。

创建tempnn.nnn之后,通过在%y$$%附加后缀(例如%y$$%.a等)来创建任意数量的进一步临时文件是很简单的。当然,假设某些其他进程不使用此过程随机创建文件名。

答案 4 :(得分:2)

我喜欢Aacini的JScript混合解决方案。只要我们从其他运行时环境借用,如何在PowerShell中使用.NET System.GUID

@echo off
setlocal

for /f "delims=" %%I in ('powershell -command "[string][guid]::NewGuid()"') do (
    set "unique_id=%%I"
)

echo %unique_id%

答案 5 :(得分:1)

很久以前,在一个名为alt.msdos.batch的galax..newsgroup中,一位撰稿人坚持要为每个引发的问题发布一个QBASIC解决方案,包装在一个批处理shell中并声称它是批处理的,因为它只使用了标准的Microsoft-提供的软件。

经过很长一段时间,他的错误方法得到了治愈,但此后我对混合方法过敏。当然 - 如果它解决问题,等等,等等 - 有时候没有逃避这个过程(例如,默默地运行批处理。)除此之外,我真的不想要学习一门新语言或者两个......这就是为什么我避开脚本解决方案的原因。 YMMV。

所以 - 这是另一种方法......

@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

基本上,将标题设置为随机的。请注意,x%random%x仅使用%random%,因此搜索" x123x"不会发现" x1234x"。

接下来,获取详细的任务列表,找到标有标题和标题的行。 findstr返回的第一行将是下划线,下一行将设置为targetline,并且任何进一步的返回都表示标题不是唯一的,因此请返回,更改它并再试一次,直到它是唯一的。

最后,获取第二列中的进程ID,注意第一列的宽度未知(因此抓住下划线的原因。)

结果:此进程的PID必须是唯一的,因此可以用作唯一临时文件名的基础 - 将其构建在为此目的保留的目录中。

答案 6 :(得分:1)

我想提交另一种方法,看看是否有任何漏洞。请告诉我。感谢。

C:>title my shell a80en333xyb

C:>type getppid.ps1
(Get-WmiObject -Class Win32_Process -Filter "processid='$pid'").ParentProcessId

C:>powershell getppid.ps1
2380

或者,在不创建.ps1文件的情况下运行它:

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

要验证是否找到了正确的任务,标题将设置为不太可能的值,tasklist.exe用于搜索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

答案 7 :(得分:0)

将文件内容作为对象,使用指针memaddress作为文件名的第一部分,使用Rand()作为第二部分。即使运行多个实例,内存地址对于所有对象也是唯一的。

答案 8 :(得分:0)

您可以尝试使用guid.bat

for /f "tokens=* delims={}" %%# in ('guid.bat') do set "unique=%%#"
echo %unique%

直接从控制台使用:

for /f "tokens=* delims={}" %# in ('guid.bat') do @set "unique=%#"

答案 9 :(得分:0)

首先,将PID用作唯一文件名的一部分的一些说明:

当您仅查找父PID时,这可能是模棱两可的,因为当您在for循环中或在批处理文件中的调用中运行该代码时,始终会创建一个新的cmd.exe并获取此临时cmd的PID .exe进程。

因此,您应该使用批处理“ root” cmd.exe进程的PID。 为了实现这一点-有另一种方法,而不是在任务列表中按标题搜索,这很慢,而且可能也模棱两可。

您可以使用Windows API函数获取批处理“ root” cmd.exe进程的PID:

  • GetConsoleWindow提供控制台窗口句柄
  • GetWindowThreadProcessId通过使用此控制台窗口句柄来提供PID

您可以使用PowerShell运行以下Windows API函数:

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();

在批处理文件中,您可以将PowerShell代码作为已编码的命令运行:

set MyPID=
for /f %%a in ('powershell -NoLogo -NoProfile -NonInteractive -EncodedCommand QQBkAGQALQBUAHkAcABlACAALQBNAGUAbQBiAGUAcgBEAGUAZgBpAG4AaQB0AGkAbwBuACAAQAAiAA0ACgBbAEQAbABsAEkAbQBwAG8AcgB0ACgAIgBrAGUAcgBuAGUAbAAzADIALgBkAGwAbAAiACwAIABFAG4AdAByAHkAUABvAGkAbgB0ACAAPQAgACIARwBlAHQAQwBvAG4AcwBvAGwAZQBXAGkAbgBkAG8AdwAiACkAXQANAAoAcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAgAGUAeAB0AGUAcgBuACAASQBuAHQAUAB0AHIAIABHAGUAdABDAG8AbgBzAG8AbABlAFcAaQBuAGQAbwB3ACgAKQA7AA0ACgBbAEQAbABsAEkAbQBwAG8AcgB0ACgAIgB1AHMAZQByADMAMgAuAGQAbABsACIALAAgAEUAbgB0AHIAeQBQAG8AaQBuAHQAIAA9ACAAIgBHAGUAdABXAGkAbgBkAG8AdwBUAGgAcgBlAGEAZABQAHIAbwBjAGUAcwBzAEkAZAAiACkAXQANAAoAcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAgAGUAeAB0AGUAcgBuACAAdQBpAG4AdAAgAEcAZQB0AFcAaQBuAGQAbwB3AFQAaAByAGUAYQBkAFAAcgBvAGMAZQBzAHMASQBkACgASQBuAHQAUAB0AHIAIABoAFcAbgBkACwAIABvAHUAdAAgAHUAaQBuAHQAIABsAHAAZAB3AFAAcgBvAGMAZQBzAHMASQBkACkAOwANAAoAcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAgAHUAaQBuAHQAIABHAGUAdABQAEkARAAoACkADQAKAHsADQAKACAASQBuAHQAUAB0AHIAIABoACAAPQAgAEcAZQB0AEMAbwBuAHMAbwBsAGUAVwBpAG4AZABvAHcAKAApADsADQAKACAAaQBmACAAKAAoAHUAaQBuAHQAKQBoAD0APQAwACkAIAByAGUAdAB1AHIAbgAgADAAOwANAAoAIAB1AGkAbgB0ACAAcgBjACAAPQAgADAAOwANAAoAIABHAGUAdABXAGkAbgBkAG8AdwBUAGgAcgBlAGEAZABQAHIAbwBjAGUAcwBzAEkAZAAoAGgALAAgAG8AdQB0ACAAcgBjACkAOwANAAoAIAByAGUAdAB1AHIAbgAgAHIAYwA7AA0ACgB9AA0ACgAiAEAAIAAtAE4AYQBtAGUAIABXAGkAbgAzADIAIAAtAE4AYQBtAGUAUwBwAGEAYwBlACAAUwB5AHMAdABlAG0ADQAKAFsAUwB5AHMAdABlAG0ALgBXAGkAbgAzADIAXQA6ADoARwBlAHQAUABJAEQAKAApADsADQAKAA^=^= 2^>nul') do (
  set "MyPID=%%~a"
)

if defined MyPID echo %MyPID%