Создать уникальное имя файла

Я видел много сообщений о создании уникального имени файла от наивного% TIME% до правдоподобного (но недостаточного)% RANDOM%. Использование wmic os get localdatetime намного лучше, но оно все равно может терпеть неудачу на нескольких CPU/основных машинах. Следующий script в конечном итоге завершится неудачей при запуске в 5+ оболочках на многоядерной машине.

@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

Есть ли у кого-нибудь надежный способ создания уникального имени файла в оболочке script?

Ответ 1

Любая система, которая полагается на случайное число, теоретически может терпеть неудачу, даже с использованием GUID. Но мир, похоже, согласился с тем, что GUID достаточно хороши. Я где-то видел код, который использует CSCRIPT JScript для генерации GUID.

Существуют и другие способы:

Сначала я предполагаю, что вы пытаетесь создать уникальный временный файл. Поскольку файл будет удален после завершения процесса, все, что вам нужно сделать, это установить исключительную блокировку ресурса, чье имя является производным от вашего имени файла temp. (на самом деле я иду наоборот - имя temp происходит от имени заблокированного ресурса).

Переназначение устанавливает исключительную блокировку выходного файла, поэтому я просто получаю имя от времени (с 0.01 секундой preciscion) и пытаюсь заблокировать файл с этим именем в папке временного пользователя. Если это не удастся, я вернусь назад и повторю попытку, пока не добьюсь успеха. Как только у меня будет успех, я гарантированно получаю право собственности на этот замок и все производные (если кто-то не намеренно нарушает систему).

Как только мой процесс завершится, блокировка будет выпущена, и временный файл с тем же именем может быть повторно использован позже. Но script обычно удаляет временный файл после завершения.

@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

Цикл из-за столкновения имен должен быть редок, если вы действительно не подчеркиваете свою систему. Если это так, вы можете уменьшить вероятность зацикливания в 10 раз, если вместо %TIME% использовать WMIC OS GET LOCALDATETIME.


Если вы ищете постоянное уникальное имя, проблема немного сложнее, так как вы не можете поддерживать блокировку на неопределенный срок. В этом случае я рекомендую использовать метод LocalDateTime для системы WMIC в сочетании с двумя проверками конфликтов имен.

Первая проверка просто проверяет, что файл еще не существует. Но это условие гонки - два процесса могут сделать проверку в одно и то же время. Вторая проверка создает файл (пустой) и устанавливает на нем временную исключительную блокировку. Хитрость заключается в том, чтобы убедиться, что блокировка поддерживается в течение определенного периода времени, который больше, чем другой процесс, чтобы проверить, существует ли файл. Я ленив, поэтому я просто использую 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

Приведенное выше должно гарантировать, что будет возвращено новое уникальное имя файла для данного пути. Существует множество возможностей для оптимизации, чтобы установить некоторую команду для выполнения во время проверки блокировки, которая занимает достаточно много времени, но не слишком длинна.

Ответ 2

Вы можете использовать certutil для base64 encode %date% %time% с семенем %random% следующим образом:

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

В случае, если один и тот же script запускается из одного и того же каталога несколькими пользователями, я добавил %username% во временные файлы, чтобы избежать дальнейших конфликтов. Я полагаю, вы могли бы заменить %random% на %username% для того же эффекта. Тогда вы получите конфликт только в том случае, если один пользователь дважды выполняет один и тот же блок кода.

(Edit: добавлено %username% как семя для уникальности.)

Ответ 3

В гибриде script 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);

Ответ 4

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

Здесь вырезана версия моего генератора tempfile для создания файла с именем tempnn.nnn в каталоге %temp%.

Как только tempnn.nnn был создан, тогда просто создать столько дополнительных tempfiles, сколько вам нравится для процесса, добавив суффикс к %y$$%, например %y$$%.a и т.д. Конечно, это предполагает, что какой-то другой процесс не произвольно создает имена файлов без использования этой процедуры.

Ответ 5

Мне нравится гибридное решение Aacini JScript. До тех пор, пока мы заимствуем из других сред исполнения, как насчет использования .NET System.GUID с помощью PowerShell?

@echo off
setlocal

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

echo %unique_id%

Ответ 6

Давным-давно в galax..newsgroup, названной alt.msdos.batch, участник настаивал на публикации решения QBASIC по каждой поднятой проблеме, завернутой в пакетную оболочку и утверждающей, что она была пакетной, поскольку она использовала только стандартный Microsoft- поставляемого программного обеспечения.

После долгого времени он был излечен от своих странствующих путей, но с тех пор я с тех пор сильно страдаю аллергией на гибридные методы. Конечно, если он исцеляет проблему, бла, бла, а иногда и не ускользает от этого курса (например, бегайте пакетно.) Кроме того, я действительно не хочу, чтобы другое изучало новый язык или два... Итак вот почему я избегаю решений * script. 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, а любые дальнейшие возвращения указывают, что заголовок не уникален, поэтому вернитесь назад, измените его и повторите попытку, пока он не станет уникальным.

Наконец, получите идентификатор процесса, который находится во втором столбце, отметив, что первая имеет неизвестную ширину (следовательно, почему подчеркивается подчеркивание.)

Результат: этот процесс PID, который должен быть уникальным и, следовательно, может использоваться как основа для уникального имени tempfile - построить его в каталоге, зарезервированном для этой цели.

Ответ 7

Я хотел бы представить другой метод и выяснить, есть ли какие-либо дыры в этом. Пожалуйста, дайте мне знать. Благодарю.

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

Ответ 8

сделать содержимое файла объектом, использовать указатель memaddress в качестве первой части имени вашего файла и Rand() в качестве вашей второй части. адрес памяти будет уникальным для всех объектов, даже при запуске нескольких экземпляров.