For fun reasons I wrote a batch-file typing 100 random upper-/lowercase letters whereever I click to. As this turned out to be quite slow I decided to start it from command line with a simple for-loop for more chars in less time:
for /L %m in (1,1,3) do (start bat.bat) starting the shell in the folder where the file is located at.
Beeing curious if I really got 300 characters I went to this website that shows the written characters in realtime and came to an interesting observation: I only get 293 letters (+- 5 or so) and not the expected 300 ones. I assume it is because of the parallel running processes. You can even see that the writing starts in blocks of 3 characters and then suddenly one or two characters are missing and this continues over the whole script duration.
Now the question: Does the parallel running of the same batch-file affect each others process?
Below is the code of the file (and yes I know it is probably done way shorter and more efficient):
#if (#CodeSection == #Batch) #then
#echo off
Setlocal EnableDelayedExpansion
timeout /t 3
set SendKeys=CScript //nologo //E:JScript "%~f0"
for /L %%G in (1,1,100) do (
set /a grkl=!random! %%2
if "x!grkl!"=="x0" (
!SendKeys! "z"
) ELSE (
!SendKeys! "Z"
)
)
exit
#end
// JScript section
var WshShell = WScript.CreateObject("WScript.Shell");
WshShell.SendKeys(WScript.Arguments(0));
I suppose it's a bug in SendKeys itself.
I simplified your code to
#if (#CodeSection == #Batch) #then
#echo off
Setlocal EnableDelayedExpansion
ping localhost -n 4 > nul
set SendKeys=CScript //nologo //E:JScript "%~f0"
for /L %%G in (1,1,100) do (
!SendKeys! "%1"
)
exit
#end
// JScript section
var WshShell = WScript.CreateObject("WScript.Shell");
WshShell.SendKeys(WScript.Arguments(0));
And for the test itself I use
(for /L %n in (1 1 2) do ( start /b geist.bat %n) ) & set /p var=
This works perfect, it outputs 200 characters.
BUT if you change geist.bat %n to a fixed value like geist.bat 1, I got less than 200 characters.
And if I change it to geist.bat Z I got Z but also lower case z characters!
It fails even when I use a fixed string in WshShell.SendKeys("P");
If I use only one thread with for /L %n in (1 1 1) all works perfect again.
So my conclusion is that the SendKey functionallity is somehow broken.
Related
This may have been solved, but I looked and couldn't find a satisfactory answer. How does one quickly calculate the length of an environment variable in a .bat script?
My problem involves environment variables of a couple thousand characters (I understand the practical limit is around 8k... the max size of a command line).
The straightforward approach simply counts characters. I'll use %path% below as an example. Assume the environment variable is known to exist -- which it does in my case -- so its length is at least 1. No special characters (eg double-quote) are involved:
#echo off& setlocal enabledelayedexpansion
set /a length=1
:loop
if not "!path:~%length%,1!"=="" set /a length+=1& goto loop
This calculates and leaves the value in environment variable 'length'.
I have seen such solutions, but they are extremely slow for longer length variables. A better approach (a couple order of magnitudes faster, and deterministic) is a binary search, such as:
#echo off& setlocal enabledelayedexpansion
set /a p2=16384, length=p2-1
:loop
if "!path:~%length%,1!"=="" set /a length-=p2
if !p2! geq 2 (set /a length+=p2/=2& goto loop) else set /a length+=1
I suppose I can live with that, but still I wonder if I'm being stupid and missing something obvious. I am looking for a pure .bat solution.
below added 6/12/2017
Learning from this method suggested by Compo led me to solution simpler/faster then dostips' (18% faster over all strings length 1 .. 8k). That seemed significant enough to post:
#echo off& setlocal enabledelayedexpansion
set "str=A%path%"
set length=0
for %%p in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!str:~%%p,1!"=="" set "str=!str:~%%p!"& set /a length+=%%p
)
PURE BATCH
#echo off
del temp.txt
for /F "delims=:" %%G in ('findstr /N "<resource>" "%~F0"') do set "start=%%G"
(for /F "usebackq skip=%start% delims=" %%G in ("%~F0") do echo %%G) > temp.txt
rem echo everything after "<resource>" - code by #Aacini
for %%G in (temp.txt) do set /a size=%%~zG - 2
rem get the file length and remove the CR\LF count
echo %size%
pause
exit/b
<resource>
things to test length
and can be mutli-line
This script echoes the string to file, and use a for loop to retrieve the file size, which is the string length. To maximize speed, code-golf it.
Some facts:
It took around .54 seconds to process a 1.04 mb long string.
Getting the length of a 1.04 MB string is more efficient than getting a string with 1-byte length.
OLD POWERSHELL
You can bypass the 8191 bytes limit by calling Powershell.
echo $characters = "yourString" > temp.ps1
echo $x= $characters.length >> temp.ps1
echo write-host $x >> temp.ps1
powershell -command "C:\PathToPS1file\temp.ps1
del /f /s /q temp.ps1
This is a slow powershell method, and requires lots of escaping.
I deal with a large text files daily, I need to add a single "-" to each line 8 characters in for instance :
ABCDEF DC01 B738
ABCDEF B787
would become
ABCDEF -DC01 B738
ABCDEF - B787
How easy is this to do with a batch file?
regards
David
#echo off
setlocal enabledelayedexpansion
(for /f "delims=" %%a in (input.txt) do (
set line=%%a
set line=!line:~0,7!-!line:~7!
echo !line!
))>output.txt
Note: this will remove empty lines and has problems with exclamation marks. (may or may not be a problem; depends on your file content)
It also will eliminate lines that begin with ; due to default EOL character.
Also, lines are limited to ~8190 bytes max length (Thanks dbenham)
Here is a "robust" pure batch solution that preserves ! and empty lines. But it is still limited to ~8190 max line length, and it is painfully slow for large files. This will not modify lines that have less than 7 characters.
#echo off
setlocal disableDelayedExpansion
set "file=file.txt"
>"%file%.new" (
for /f "delims=" %%A in ('findstr /n "^"` "file.txt"') do (
set "s=%%A"
setlocal enableDelayedExpansion
set "s=!s:*:=!"
if not defined s (
echo(
) else if "!s:~7!" equ "" (
echo(!s!
else (
echo(!s:~0,7!-!s:~7!
)
endlocal
)
)
move /y "%file%.new" "%file%" >nul
And here is a truly robust and fast solution that uses JREPL.BAT - a regular text processing utility that is pure script (batch/JScript) that runs natively on any Windows machine from XP onward - no 3rd party executable required. This solution also does not modify lines that have fewer than 7 characters.
From the command line:
jrepl "^.{7}" "$&-" /f file.txt /o -
From within a batch script
call jrepl "^.{7}" "$&-" /f file.txt /o -
This is a simple two-lines Batch-JScript hybrid script solution that have all the advantages of JScript language: is fast, robust, manage all special characters and preserve empty lines. Besides, it is simple enough to be understood and then modified for other similar tasks after read the documentation. Copy it to a file with .BAT extension.
#set #a=0 // & cscript //nologo //E:JScript "%~F0" < input.txt > output.txt & goto :EOF
WScript.Stdout.Write(WScript.Stdin.ReadAll().replace(/^.{7}/gm,"$&-"));
I'm playing around with the new (limited) support for VT-100 escape sequences within the Windows 10 console. The supported sequences are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx.
In particular, the following sequence that reports the current cursor position interests me.
ESC[6n - responds with ESC[<n>;<m>R,
where <n> is the row number, and <m> the column number
The response is passed off as keyboard input, and appears on the screen, but I have no idea how to programmatically make use of the information. Ideally I would like to get the <n> and <m> values into environment variables from within a batch file.
But if anyone can demonstrate how to capture the variables using any language, then I may be able to use that knowledge to develop an effective batch file strategy.
I can get close with the following simple script called ANSI.BAT
#echo off
setlocal enableDelayedExpansion
for /f "delims=" %%C in (
'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(0x1B"'
) do set "esc=%%C"
set "csi=%esc%["
echo(Inquiry:%csi%6n
set /p "pos="
echo response=!pos:%esc%=ESC!
--OUTPUT--
C:\test>ansi
Inquiry:
^[[3;9R
response=ESC[3;9R
C:\test>
I can easily parse out the values using FOR /F, once I have the response in a variable. The problem I am having is I must manually press the <Enter> key after the response appears on the screen in order to terminate the input for my SET /P statement. I am stumped on where to go from here...
EDIT - One last requirement: I don't want the inquiry response to appear on the screen, as that disrupts the screen, and changes the cursor position. I suspect this may be the toughest nut to crack, perhaps impossible with pure batch.
Major change after three years
It works with reading the response by using XCOPY or REPLACE.
I'm using replace here, to avoid language dependent problems.
#echo off
for /F "delims=#" %%a in ('"prompt #$E# & for %%a in (1) do rem"') do set "ESC=%%a"
call :get_cursor_pos
exit /b
:get_cursor_pos
set "response="
set pos=2
:_get_loop
REM *** Request Cursor position
<nul set /p "=%ESC%[6n"
FOR /L %%# in (1 1 %pos%) DO pause < CON > NUL
for /F "tokens=1 skip=1 eol=" %%C in ('"REPLACE /W ? . < con"') DO (
set "char=%%C"
)
set "response=%response%%char%"
set /a pos+=1
if "%char%" NEQ "R" goto :_get_loop
set response
exit /b
The main problem is, XCOPY or REPLACE allows me to read one character from the input stream, but then clears the remaining buffer.
Conversely, PAUSE reads one character, preserving the remaining buffer, but does not reveal what character was read.
To solve this, I issue the query multiple times, reading a different character of the response each time. For each iteration I use a combination of 2 or more PAUSE statements followed by REPLACE to read a specific character of the response. Each iteration uses one more PAUSE than the prior iteration, until I am able to read the terminating R.
I developed this technique and initially posted it at DosTips - Query States using Console Virtual Terminal Sequences.
I have NOT Windows 10, so I can't complete any test. However, if the response of the Ansi ESC[6n sequence is fill the keyboard input buffer with ESC[<n>;<m>R characters, then it is just necessary to add an Enter key to such input in order to read it via SET /P command, and this can be done via SendKeys JScript method.
I also used a simpler method to get an ESC character in a variable.
EDIT: I modified the code accordingly to comments...
#if (#CodeSegment == #Batch) #then
#echo off
title Ansi Test
setlocal EnableDelayedExpansion
for /F %%a in ('echo prompt $E ^| cmd') do set "esc=%%a"
set "csi=%esc%["
echo Inquiry:%csi%6n
cscript //nologo //E:JScript "%~F0"
set /p "pos=" > NUL
echo response=!pos:%esc%=ESC!
#end
var sh = WScript.CreateObject("WScript.Shell");
sh.AppActivate("Ansi Test");
sh.SendKeys("{ENTER}");
Please, post the result...
How do I loop through ASCII values (here, alphabets) and work on them?
I want to echo A to Z without having to type every character manually like
for %%J in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do echo %%J
So I was wondering if I can cycle through ASCII codes. Something like for %%J in (ASCII of A ie.65 to Z) do echo %%J
Any help would be appreciated.
Surprisingly, there is a solution that makes use of an undocumented built-in environment variable named =ExitCodeAscii, which holds the ASCII character of the current exit code1 (ErrorLevel):
#echo off
for /L %%A in (65,1,90) do (
cmd /C exit %%A
call echo %%^=ExitCodeAscii%%
)
The for /L loop walks through the (decimal) character codes of A to Z. cmd /C exit %%A sets the return code (ErrorLevel) to the currently iterated code, which is echo-ed as a character afterwards. call, together with the double-%-signs introduce a second parsing phase for the command line in order to get the current value of =ExitCodeAscii rather than the one present before the entire for /L loop is executed (this would happen with a simple command line like echo %=ExitCodeAscii%). Alternatively, delayed expansion could be used also.
The basic idea is credited to rojo and applied in this post: How do i get a random letter output in batch.
1) The exit code (or return code) is not necessarily the same thing as the ErrorLevel value. However, the command line cmd /C exit 1 sets both values to 1. To ensure that the exit code equals ErrorLevel, use something like cmd /C exit %ErrorLevel%.
Loop through ASCII codes in batch file
Any batch solution to do what you want would be much more complex than what you already have.
Consider using PowerShell instead:
for($i=65;$i -le 90; $i++){[char]$i}
Example output:
PS F:\test> for($i=65;$i -le 90; $i++){[char]$i}
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
This answer is a compilation and comparison of the methods given before.
In the question the OP requested to echo A to Z letters in a batch file. If the purpose of the solution is not just show letters, but process the letters in any other way, then the =ExitCodeAscii variable method is the only one that do that with Batch code, although the modification required in the other two methods to do the same is simple.
The code below include the three methods and compare they in the simplest possible way: via the time required by each one to complete.
#if (#CodeSection == #Batch) #then
#echo off
setlocal
set "start=%time%"
for /L %%a in (65,1,90) do (
cmd /C exit %%a
call echo Batch: %%^=ExitCodeAscii%%
)
echo First method: using =ExitCodeAscii variable
echo Start: %start%
echo End: %time%
echo/
set "start=%time%"
for /F %%a in ('powershell "65..90 | %%{ [char]$_ }"') do echo PS: %%a
echo Second method: using PowerShell
echo Start: %start%
echo End: %time%
echo/
set "start=%time%"
for /F %%a in ('cscript //nologo //E:JScript "%~F0"') do echo JScript: %%a
echo Third method: using JScript
echo Start: %start%
echo End: %time%
goto :EOF
#end
for (var i=65; i<=90; i++) WSH.Echo(String.fromCharCode(i));
Several executions of this code show consistent results: the PowerShell method is the slowest one, the =ExitCodeAscii method run 280% faster than PowerShell, and JScript method run 240% faster than =ExitCodeAscii. These differences would diminish as the whole program grow and perform more things than just show the letters, but in standard/small Batch files, this relation will always be the same: PowerShell is the slowest method and JScript the fastest one. The VBScript method is similar to JScript.
In Vbscript, you can do something like this :
For i = 65 To 90
Car = Car & Chr(i) & vbTab
Next
wscript.echo Car
And you can generate it with a batch file like this :
#echo off
Call :vbs
Pause
exit /b
:vbs
(
echo For i = 65 To 90
echo Car = Car ^& Chr(i^) ^& vbTab
echo Next
echo wscript.echo Car
)>"%tmp%\%~n0.vbs"
Cscript /NoLogo "%tmp%\%~n0.vbs"
The simplest solution is to include this PowerShell one-liner in your bat script:
powershell "[char[]](65..90)"
It's not necessarily the fastest, though.
Here's a VBScript solution similar to Hackoo's, but in a hybrid format not relying on writing an external .vbs file. Save it with a .bat extension. The cscript line is what triggers execution of the VBScript hybrid code.
<!-- : batch portion
#echo off & setlocal
cscript /nologo "%~f0?.wsf"
goto :EOF
: VBScript -->
<job>
<script language="VBScript">
For i = 65 to 90
WSH.Echo(Chr(i))
Next
</script>
</job>
Or if you're more comfortable with JavaScript syntax, here's a Batch + JScript hybrid.
#if (#CodeSection == #Batch) #then
#echo off & setlocal
cscript /nologo /e:JScript "%~f0"
goto :EOF
#end // end Batch / begin JScript
for (var i=65; i<=90; i++) WSH.Echo(String.fromCharCode(i));
Is there any way to CLS a single line of output? I don't believe there are any switches for CLS, so maybe a better question would be:
Is there any way to
retain all previous output for re-use?
or
capture currently displayed output (like you can by marking and copying)?
I'm just trying to make my scripts a little more user-friendly by having real-time feedback / information, instead of multiple lines with slight changes. The only way I can think of doing this, though, is like this:
#echo off
goto Prep
:Prep
SET count=5
SET genericMessage=This window will close
goto Output
:Output
IF NOT %count% == -1 (
cls
IF %count% == 0 (
echo %genericMessage% now.
) ELSE (
echo %genericMessage% in %count% seconds.
)
SET /A count=%count% - 1
ping localhost -n 2 >nul
goto Output
) ELSE (
exit
)
So, you get this:
The problem with this, though, is that CLS erases all output, when I only want to refresh one line by erasing and re-outputting it.
Anyone have any ideas?
If you only need to move the cursor in one line (like your sample),
it's possible with a carriage return character.
#echo off
setlocal EnableDelayedExpansion
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
for /L %%n in (5 -1 1) do (
<nul set /P "=This window will close in %%n seconds!CR!"
ping -n 2 localhost > nul
)
Unfortunately, there is no native command or utility that repositions your cursor in a Windows command line console.
You will need a 3rd party utility.
Aacini posted a free CursorPos.exe utility on DOSTips. The CurorPos.exe "source" is given as Hex digits. To use the source you will need the HexToBin.bat "compiler".
Browse both threads and you will find a number of utilities you may find useful.
Try ANSI sequences: http://www.robvanderwoude.com/ansi.php
Burrowing down the links, http://batch.xoo.it/t2238-BG-exe-Utility-for-Batch-Games.htm looks the most promising.
This page sounds like it has useful discussion on controlling/setting console sizes (and other display and buffer size settings). http://www.pcreview.co.uk/forums/change-buffer-size-console-window-can-runas-inherit-console-props-t1468842.html
An alternative quick&dirty method of moving the cursor via TIMEOUT:
#echo off
<nul set/p"=5 seconds till i close..."
timeout /t 5 /nobreak >con
echo(i'm closing now...[REPLACE this with lots of spaces]
exit /b