2015-01-06 4 views
8

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

@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 

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

+0

Не так часто возникает вопрос в [тег: batch-file] land, который требует более 5 ответов. +1 интересный вопрос! – rojo

ответ

10

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

Есть и другие способы:

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

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

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

@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 раз, если используете WMIC OS GET LOCALDATETIME вместо %TIME%.


Если вы ищете постоянного уникального имени, то проблема немного сложнее, так как вы не можете поддерживать блокировку на неопределенный срок. В этом случае я рекомендую использовать метод 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 

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

+1

Идея вашего файла блокировки кажется самым надежным решением. Принуждение каждого экземпляра к созданию эксклюзивной блокировки гарантирует, что никакие два параллельных экземпляра никогда не смогут использовать один и тот же файл. Он невосприимчив к условиям гонки. Если бы я был O.P., это выиграло бы. – rojo

+0

Согласен. Это, по-видимому, самое промышленное прочное решение. – lit

+0

@dbenham - Я пересматриваю эту тему. Проблема в том, что мой «Ваш код» должен вернуть код выхода. Если он возвращает ненулевое значение, управление передается в цикл для получения уникального имени. – lit

4

Вы можете использовать certutil для base64 кодирования %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% 

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

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

+0

Будет ли certutil всегда производить тот же вывод, что и входной сигнал такой же? Если это так, то если исходный текст,% date %% time %% random%, не уникален, тогда кодирование не поможет. В моих экспериментах исходный текст не всегда уникален. – lit

+0

Сколько потоков, вероятно, будет работать в течение одной и той же секунды одним и тем же пользователем? Вы можете добавить '% username%' в качестве семени. Или, если вы хотите увеличить случайность '% random%', тогда 'set/a 'rand = (% random% + 1) * (% random% + 1) *% random%" 'должно привести к случайному целому числу в полностью подписанном 32-битном диапазоне от -2147483648 до 2147483647. (Потому что, когда число превышает 32 бита, интерпретатор cmd переходит к отрицательным числам.) Правда, что есть некоторый вес, и это не приведет к производным от простых чисел , но тем не менее вы все равно получаете больше случайности. – rojo

0

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

+0

Хорошие идеи, если бы я использовал такой язык, как C/C++, Python, Perl, Java и т. Д. Это сценарий пакетного файла DOS/Windows. Я могу написать эти другие вещи, но стараюсь не делать этого. – lit

+0

Я не вижу, как это может быть гарантировано быть уникальным, если на общем устройстве написано несколько машин. – dbenham

+0

Если вы используете пакетный скрипт, то, возможно, «имя машины - идентификатор процесса - отметка времени - случайное число» должно быть достаточно уникальным. –

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

Вот сокращенная версия моего TempFile генератора, чтобы создать файл с именем tempnn.nnn в каталоге %temp%.

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

+0

Мне нравится, что ваш метод делает чертовски уверен, что файл не существует с 'if exist' и loop. – rojo

+1

Я считаю, что здесь есть две проблемы. Во-первых,% random% часто не является случайным. Во-вторых, время между тестом (если существует) и множеством (копия нуль) оставляет возможность для отказа. – lit

+0

@Paul: Зацикливание на файл-существует, заботясь о проблеме rndom-isn't-random. Конечно, существует теоретическая возможность того, что файл может отображаться между тестом и копией, но пакет не может справиться с этим. Он не может создать и затем протестировать, так как он может затем убить действительный файл, созданный другим процессом, поэтому мы остаемся с методом test-and-create. Ограничение языка - и если вы не можете гарантировать, что ваш процесс не прерывается, ограничение любого языка. – Magoo

4

Гибридный сценарий Пакетный-JScript ниже использует fso.GetTempName WSH в() метод, который был разработан именно для этой цели:

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

Это выглядит довольно хорошо. Я, вероятно, выберу это как ответ после некоторого тестирования в интеграции в более крупный проект. – lit

+1

Сначала я думал, что имя, сгенерированное GetTempName(), будет гарантировано уникальным. Но, основываясь на моем чтении различных неофициальных источников, кажется, что возвращаемое имя - просто случайное число, без какой-либо истинной проверки, что оно уникально. Я думаю, вам лучше использовать JScript для генерации GUID. – dbenham

+1

@dbenham: Не имеет значения, генерирует ли 'GetTempName()' уникальное имя или нет, потому что дополнительный тест 'FileExists (fileName)' test гарантирует, что файл уникален. 'GetTempName()' - просто метод, более простой в использовании, чем комбинация случайных/временных значений. – Aacini

1

Мне нравится гибридное решение 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% 
1

Уже давно в galax..newsgroup называется alt.msdos.batch, участник настаивал на размещение QBasic решения каждого вопрос, поднятый, обернутый в пакетной оболочке и утверждая, что это была партия потому что это только используемое стандартное программное обеспечение 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, и любые дальнейшие возвращения указывают, что заголовок не уникален, поэтому вернитесь назад, измените его и повторите попытку, пока он не станет уникальным.

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

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

+0

Пока я пытался получить PID раньше, я никогда не приходил к работоспособному решению. Экран, очищающий вывод tasklist.exe, выглядит так, как будто он будет работать. Но, я думаю, Powershell может быть проще и понятнее. – lit

1

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

C:>title my shell a80en333xyb 

C:>type getppid.ps1 
(gwmi win32_process -Filter "processid='$pid'").ParentProcessId 

C:>powershell getppid.ps1 
2380 

Или, чтобы запустить его без создания .PS1 файла:

powershell -NoProfile -Command "(gwmi 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 
Смежные вопросы