バックグラウンドで何かが行われていることをユーザーにスピナーで示したいのですが、これがバッチファイルでどのように機能するのかわかりません。
誰も手がかりを持っていますか?
バックグラウンドで何かが行われていることをユーザーにスピナーで示したいのですが、これがバッチファイルでどのように機能するのかわかりません。
誰も手がかりを持っていますか?
これは実際には、純粋なネイティブ コマンドを使用して非常に簡単に実行できます。よりトリッキーなコマンドの使用方法を知っている必要があります。VBScript などの外部ツールの使用や、画面のクリアなどの厄介な副作用は必要ありません。
あなたが探しているのはecho -n、改行なしで行を出力する bash " " コマンドに相当するものです。XP バッチでは、これはset /p次のように空の入力で " " (プロンプトでユーザーに応答を求める) を使用することで実現されます。
<nul (set /p junk=Hello)
echo. again.
文字列「Hello again」を出力します。間に改行はありません。
そのトリック (および CTRL-H の使用、バックスペース文字は、20 秒のタイムアウトと 15 秒のサブタスクで 10 秒のサブタスクを (次々に) 開始する次のテスト スクリプトで確認できます)。 10 秒のタイムアウトのタスク。
ペイロード スクリプトは、実際に実行中のスクリプトによって作成されます。唯一の要件は、必要な作業を実行し、終了時にフラグ ファイルを削除して、監視機能がそれを検出できるようにすることです。
このスクリプトの ^H 文字列は、実際には CTRL-H 文字であることに注意してください。パイプ記号をエスケープするために使用される 2 つの別個の文字です。
@echo off
:: Localise environment.
setlocal enableextensions enabledelayedexpansion
:: Specify directories. Your current working directory is used
:: to create temporary files tmp_*.*
set wkdir=%~dp0%
set wkdir=%wkdir:~0,-1%
:: First pass, 10-second task with 20-second timeout.
del "%wkdir%\tmp_*.*" 2>nul
echo >>"%wkdir%\tmp_payload.cmd" ping 127.0.0.1 -n 11 ^>nul
echo >>"%wkdir%\tmp_payload.cmd" del "%wkdir%\tmp_payload.flg"
call :monitor "%wkdir%\tmp_payload.cmd" "%wkdir%\tmp_payload.flg" 20
:: Second pass, 15-second task with 10-second timeout.
del "%wkdir%\tmp_*.*" 2>nul:
echo >>"%wkdir%\tmp_payload.cmd" ping 127.0.0.1 -n 16 ^>nul
echo >>"%wkdir%\tmp_payload.cmd" del "%wkdir%\tmp_payload.flg"
call :monitor "%wkdir%\tmp_payload.cmd" "%wkdir%\tmp_payload.flg" 10
goto :final
:monitor
:: Create flag file and start the payload minimized.
echo >>%2 dummy
start /min cmd.exe /c "%1"
:: Start monitoring.
:: i is the indicator (0=|,1=/,2=-,3=\).
:: m is the number of seconds left before timeout.
set i=0
set m=%3
<nul (set /p z=Waiting for child to finish: ^|)
:: Loop here awaiting completion.
:loop
:: Wait one second.
ping 127.0.0.1 -n 2 >nul
:: Update counters and output progress indicator.
set /a "i = i + 1"
set /a "m = m - 1"
if %i% equ 4 set i=0
if %i% equ 0 <nul (set /p z=^H^|)
if %i% equ 1 <nul (set /p z=^H/)
if %i% equ 2 <nul (set /p z=^H-)
if %i% equ 3 <nul (set /p z=^H\)
:: End conditions, complete or timeout.
if not exist %2 (
echo.
echo. Complete.
goto :final
)
if %m% leq 0 (
echo.
echo. *** ERROR: Timed-out waiting for child.
goto :final
)
goto :loop
:final
endlocal
これを試して:
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
CALL :BACKSPACE $BS
SET /A FULL_COUNT=60
SET /A MAX_COUNT=160
SET /A Spin_Delay=50
SET "_MSG=Process running..."
SET /A CTR=0
SET /A TCT=0
IF NOT [%1]==[] SET _MSG=%~1
IF NOT [%2]==[] SET /A FULL_COUNT=%2
IF NOT [%3]==[] SET /A SPIN_DELAY=%3
IF %FULL_COUNT% GTR %MAX_COUNT% SET FULL_COUNT=%MAX_COUNT%
(SET/P=%_MSG%*)<nul
FOR /L %%A IN (1,1,%FULL_COUNT%) DO (
CALL :DELAY %SPIN_DELAY%
IF !CTR! EQU 0 (set/p=%$BS%³)<nul
IF !CTR! EQU 1 (set/p=%$BS%/)<nul
IF !CTR! EQU 2 (set/p=%$BS%Ä)<nul
IF !CTR! EQU 3 (set/p=%$BS%\)<nul
SET /A CTR=%%A %% 4
)
(SET/P=%$BS%*)<nul
ENDLOCAL & EXIT /B %CTR%
:BackSpace
setlocal
for /f %%a in ('"prompt $H$S &echo on &for %%b in (1) do rem"') do set "Bs=%%a"
endlocal&call set %~1=%BS%&exit /b 0
:Delay msec
setlocal enableextensions
set/a correct=0
set/a msecs=%1+5
if /i %msecs% leq 20 set /a correct-=2
set time1=%time: =%
set/a tsecs=%1/1000 2>nul
set/a msecs=(%msecs% %% 1000)/10
for /f "tokens=1-4 delims=:." %%a in ("%time1%") do (
set hour1=%%a&set min1=%%b&set sec1=%%c&set "mil1=%%d"
)
if /i %hour1:~0,1% equ 0 if /i "%hour1:~1%" neq "" set hour1=%hour1:~1%
if /i %min1:~0,1% equ 0 set min1=%min1:~1%
if /i %sec1:~0,1% equ 0 set sec1=%sec1:~1%
if /i %mil1:~0,1% equ 0 set mil1=%mil1:~1%
set/a sec1+=(%hour1%*3600)+(%min1%*60)
set/a msecs+=%mil1%
set/a tsecs+=(%sec1%+%msecs%/100)
set/a msecs=%msecs% %% 100
:: check for midnight crossing
if /i %tsecs% geq 86400 set /a tsecs-=86400
set/a hour2=%tsecs% / 3600
set/a min2=(%tsecs%-(%hour2%*3600)) / 60
set/a sec2=(%tsecs%-(%hour2%*3600)) %% 60
set/a err=%msecs%
if /i %msecs% neq 0 set /a msecs+=%correct%
if /i 1%msecs% lss 20 set msecs=0%msecs%
if /i 1%min2% lss 20 set min2=0%min2%
if /i 1%sec2% lss 20 set sec2=0%sec2%
set time2=%hour2%:%min2%:%sec2%.%msecs%
:wait
set timen=%time: =%
if /i %timen% geq %time2% goto :end
goto :wait
:end
for /f "tokens=2 delims=." %%a in ("%timen%") do set num=%%a
if /i %num:~0,1% equ 0 set num=%num:~1%
set/a err=(%num%-%err%)*10
endlocal&exit /b %err%
画面のクリアが気にならない場合は...これを試してください:
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET COUNT=1
START CALC
:BEGIN
CLS
IF !COUNT! EQU 1 ECHO \
IF !COUNT! EQU 2 ECHO -
IF !COUNT! EQU 3 ECHO /
IF !COUNT! EQU 4 ECHO -
IF !COUNT! EQU 4 (
SET COUNT=1
) ELSE (
SET /A COUNT+=1
)
PSLIST CALC >nul 2>&1
IF %ERRORLEVEL% EQU 1 GOTO END
GOTO BEGIN
:END
編集: このサンプルは電卓を起動し、電卓を閉じるまで「スピナー」を表示します。pslistを使用して、CALC.EXE の存在を確認します。>nul 2>&1はSTDOUT と STDERR を nul にリダイレクトするため、PSLIST からは何も表示されません。
あなたの質問を理解したら、あなたが実行している操作に時間がかかり、何かが起こっていることをユーザーに示したいので、スピナーが必要ですよね?
その場合、私が知る限り、ネイティブ コマンドでは不可能です。(時間がかかる操作を実行中にスピナーを表示するプログラムがあれば可能かもしれません)
また、echo は ansi エスケープ シーケンスをサポートしていないようです (昔は ansi.sys をロードする必要がありましたが、まだ存在するかどうかはわかりません)。そのため、ansi を使用してカーソルを制御することはできません。
Windows バッチ スクリプト内を意味する場合は、ネイティブに行うことはできません。コンソールへの出力に使用される echo ステートメントは常に改行を出力し、カーソルを移動することはできません。
少しハックですが、VBScript とバッチ スクリプトを組み合わせてこれを行うことができます。
この VBScript はバックスペースを出力し、次に引数です。
WScript.StdOut.Write(chr(8) & WScript.Arguments(0))
これをファイルに入れてvbsEcho.vbs、バッチ スクリプトからこのスクリプトを呼び出します。次のバッチ スクリプトは、CTRL-C を押すまでスピナーを表示し続けます。
@echo off
:LOOP
cscript //nologo vbsEcho.vbs "\"
cscript //nologo vbsEcho.vbs "|"
cscript //nologo vbsEcho.vbs "/"
cscript //nologo vbsEcho.vbs "-"
goto :LOOP
編集: aphoria's answer のいくつかのアイデアを使用して、このスクリプトは Windows 電卓を起動し、電卓が閉じるまでスピナーを表示します。
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET COUNT=1
START CALC
cscript //nologo vbsEcho.vbs "Calculating: \"
:LOOP
IF !COUNT! EQU 1 cscript //nologo vbsEcho.vbs "|"
IF !COUNT! EQU 2 cscript //nologo vbsEcho.vbs "/"
IF !COUNT! EQU 3 cscript //nologo vbsEcho.vbs "-"
IF !COUNT! EQU 4 (
cscript //nologo vbsEcho.vbs "\"
set COUNT=1
) else (
set /a COUNT+=1
)
pslist CALC >nul 2>&1
if %ERRORLEVEL% EQU 1 goto :end
goto :LOOP
:END
cscript //nologo vbsEcho.vbs ". Done."
スピナーはバッチスクリプトで実行できます。いくつかの変数が必要なだけです。
@echo off
:spinner
set mSpinner=%mSpinner%.
if %mSpinner%'==..............................' set mSpinner=.
cls
echo %mSpinner%
rem Check if the process has finished via WMIC and/or tasklist.
goto spinner
:exit
BAT 自体がプロセスの実行中/終了を検出するため。これは、 WMI コマンドライン インターフェイスまたは tasklist コマンドを使用して行うことができますが、これについては私にはあまり詳しくありません。
DOS の時代に戻っていれば、画面をクリアせずにそれを行うことさえできました...エスケープ文字の組み合わせを使用することはできませんでした。Vista/XPでまだ可能かどうかはわかりません。
タイトルを更新するのが最も簡単な方法だと思います。そうすれば、常にCLSを実行する必要がなくなります。
ping -nの2行の理由は、2秒の1回のpingよりも、1秒ごとに2回のpingを実行する方がpingの方が速いためです。
また、知らない人にとっては、::はREMと同じですが、パーサーの最後ではなく最初のコメントが無視される点が異なります(これは正しい単語だと思います)。簡単に言えば、その行は無視されます。
:: begin spin.cmd
@echo off
setlocal
set COUNT=0
set MAXCOUNT=10
set SECONDS=1
:LOOP
title "\"
call :WAIT
title "|"
call :WAIT
title "/"
call :WAIT
title "-"
if /i "%COUNT%" equ "%MAXCOUNT%" goto :EXIT
set /a count+=1
echo %COUNT%
goto :LOOP
:WAIT
ping -n %SECONDS% 127.0.0.1 > nul
ping -n %SECONDS% 127.0.0.1 > nul
goto :EOF
:EXIT
title FIN!
endlocal
:: end spin.cmd
特定のセットとは異なる文字 ("\|/-" など) を出力するカウンターを使用でき、"counter modulo 4" などに従って文字を変更できます。とにかく、どの言語で作業しているかは言わないので、正確に言うのは少し難しいです。
編集: どの環境でプレイしているかがわかったので、BAT/CMD 言語は実際にはそのタスクに対応していないと思います...スクリプト言語をお勧めしますが、Ruby が私のお気に入りです。
このルーチンは、cmd から開始したプロセスの tasklist の出力を調べます。
パラメータ
として
exe
の名前を渡し
ます
。
Elapsed 001 seconds メッセージは、ECHO.exe -n \r によって毎秒上書きされ
、改行なしで cr がエコーされます。
Echo.exe は
http://www.paulsadowski.com/wsh/cmdprogs.htmで入手できます。
@echo off
start calc
call :spinner calc.exe
pause
:spinner
SET COUNT=1
:BEGIN
set "formattedValue=000000%count%"
ECHO.exe -n Elapsed: %formattedValue:~-3% seconds
ECHO.exe -n \r %= -n (suppress crlf) \r output a cr =%
SET /A COUNT+=1
set EXE=%1 %= search output of tasklist for EXE =%
set tl=tasklist /NH /FI "IMAGENAME eq %EXE%"
FOR /F %%x IN ('%tl%') DO IF %%x == %EXE% goto FOUND
set result=0
goto FIN
:FOUND
set result=1
:FIN
IF %result% EQU 0 GOTO END
PING -n 2 127.0.0.1 > nul %= wait for about 1 second =%
GOTO BEGIN
:END
paxdiablos には素晴らしい答えがありますが、コマンドをペイロード ファイルにエコーしなければならないのは面倒です。読みにくく、デバッグが困難です。私は彼のコードを取得し、自分で使用するために少し変更しました。
@echo off
:: Localise environment.
setlocal enableextensions enabledelayedexpansion
set wkdir=%~dp0%
set wkdir=%wkdir:~0,-1%
set done_flag="%wkdir%\tmp_payload.flg"
set timeout=7
:controller
IF (%1)==() (
call :monitor step1 "Getting stuff from SourceSafe: "
call :monitor step2 "Compiling some PHP stuff: "
call :monitor step3 "Finishing up the rest: "
) ELSE ( goto %1 )
goto final
:step1
::ping for 5 seconds
ping 127.0.0.1 -n 6 >nul
del "%wkdir%\tmp_payload.flg"
goto final
:step2
::ping for 10 seconds
ping 127.0.0.1 -n 11 >nul
del "%wkdir%\tmp_payload.flg"
goto final
:step3
::ping for 5 seconds
ping 127.0.0.1 -n 6 >nul
del "%wkdir%\tmp_payload.flg"
goto final
:monitor
:: Create flag file and start the payload minimized.
:: echo the word "dummy" to the flag file (second parameter)
echo >>%done_flag% dummy
:: start the command defined in the first parameter
start /min cmd.exe /c "test2.bat %1"
:: Start monitoring.
:: i is the indicator (0=|,1=/,2=-,3=\).
:: m is the number of seconds left before timeout.
set i=0
set m=%timeout%
set str=%2
for /f "useback tokens=*" %%a in ('%str%') do set str=%%~a
<nul (set /p z=%str%^|)
:: Loop here awaiting completion.
:loop
:: Wait one second.
ping 127.0.0.1 -n 2 >nul
:: Update counters and output progress indicator.
set /a "i = i + 1"
set /a "m = m - 1"
if %i% equ 4 set i=0
if %i% equ 0 <nul (set /p z=^|)
if %i% equ 1 <nul (set /p z=/)
if %i% equ 2 <nul (set /p z=-)
if %i% equ 3 <nul (set /p z=\)
:: End conditions, complete or timeout.
if not exist %done_flag% (
::echo.
echo Complete
goto :final
)
if %m% leq 0 (
echo.
echo. *** ERROR: Timed-out waiting for child.
goto :final
)
goto :loop
:final
endlocal
:LOOP ECHOX -n "〜r%Processing ..." IF%CTR%EQU 4 SET / A CTR = 0 IF%CTR%== 0(set / pDOT =³)