I have a very small batch script which is extracting a quite amount of files.
The script is meant to be delivered with the compressed data to other users.
Now my problem is that this compression tool is outputting a ton of data into the cmd window.
I think this will confuse a lot of useser because the output is really "running". It basically shows a percentage with each line and how it decompressed at which speed (CPU and HDD).
A lot of confusing data that no one needs to see. Now I don't really like suppressing all the output of the program, giving the user feedback on how far the decompression already got would be important in my opinion.
So is it possible to redirect the output and read just the first three digits of that output and deliver that to the users in a single line? So the users only sees an advancing percantage (in one line) and not 20 new lines every second with all this data?
Here an example of how it looks at the moment:
http://i.imgur.com/5w5LH.png
The compression tool is SREP, my OS Win 7 x64.
If you use windows batch, it can be done, but it's not simple, as you would normally do this with a FOR/F-Loop.
Like for /f "delims=" %%a in (7z packit ...) do ...
The problem is here, that the for-loop will first collect all data and wait for the end of 7z before it process any line of the output.
The only way is to redirect the output, and to scan it simultaneously.
But to do that you need a second thread (at best in the same cmd-window).
Something like this would do the job
#echo off
echo start
setlocal EnableDelayedExpansion
if "%1"=="internal" goto :readThread
start /b cmd /c ""%~f0" internal"
rem *** myPacker pack here and redirect the output
del archive.7z
del output.tmp
start "title" /b /wait cmd /c "%ProgramFiles%\7-Zip\7z" u archive \tempx\*.rtf \tempx\*.pdf ^> output.tmp
echo EOF>>output.tmp
echo ENDE
:waitForEnd
(
> lock (
rem
) || goto :waitForEnd
) 2> nul
exit /b
:readThread
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
echo ####!cr!x
echo(
<output.tmp (
echo ## before
call :readData 2> lock
echo after
)
exit /b
:readData
set "var="
set /p var=
if "!var!"=="EOF" exit /b
if defined var (
<nul set /p ".=Processing files, currently at !var:~0,4!!CR!"
)
goto :readData
Here is a simple hybrid batch/JScript script that I think will do what you want.
show3.bat
#if (#X)==(#Y) #end /* harmless hybrid line that begins a JScrpt comment
::: Batch part ::::
#cscript //nologo //e:JScript "%~f0"
#exit /b
*** JScript part ***/
while( !WScript.StdIn.AtEndOfStream ) {
WScript.StdOut.Write( '\x08\x08\x08' + WScript.StdIn.ReadLine().substr(0,3) );
}
WScript.StdOut.WriteLine();
Usage:
yourCommand | show3
The script could be simplified to pure JScript, but then it won't be as convenient to use:
show3.js
while( !WScript.StdIn.AtEndOfStream ) {
WScript.StdOut.Write( '\x08\x08\x08' + WScript.StdIn.ReadLine().substr(0,3) );
}
WScript.StdOut.WriteLine();
Usage:
yourCommand | cscript //nologo show3.js
EDIT As jeb commented, you should not need any redist to use this solution.
I've taken some of the concepts in jeb's answer and combined the entire process into one hybrid script. No need for a standalone "show3" file.
#if (#X)==(#Y) #end /* harmless hybrid line that begins a JScrpt comment
:: ***** Batch part ******
#echo off
REM whatever batch code you need goes here
yourCommand | cscript //nologo //e:JScript "%~f0"
REM whatever batch code you need goes here
exit /b
****** JScript part ******/
while( !WScript.StdIn.AtEndOfStream ) {
WScript.StdOut.Write( '\x08\x08\x08' + WScript.StdIn.ReadLine().substr(0,3) );
}
WScript.StdOut.WriteLine();
yourCommand would be whatever compression command you are using. Based on your comments, it sounds like you might have to use yourCommand 2>&1 if the output you want is printing to stderr instead of stdout.
I've created a "yourCommand.bat" file for testing purposes. It crudely emulates the output behavior you describe for your compression program.
#echo off
for /l %%A in (1 1 100) do (
echo %%A "I don't want to see this quoted text"
for /l %%B in (1 1 50000) do rem
)
Finally, if you really want a pure batch solution, I greatly simplified jeb's solution. I eliminated the temp file and used a pipe instead.
#echo off
if "%~1"==":show3" goto :show3
REM whatever batch code you need goes here
(yourCommand & echo EOF) | "%~f0" :show3
REM whatever batch code you need goes here
exit /b
:show3
setlocal enableDelayedExpansion
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
:read
set "ln="
set /p "ln="
if not defined ln goto :read
for /f "tokens=1* delims= " %%A in ("!ln!") do if "%%A%%B" equ "EOF" (
echo(
exit /b
)
<nul set /p "=!ln:~0,3! !cr!"
goto :read
Edit - I modified the EOF test to ignore any leading or trailing spaces. This should make the code more robust.
Related
Obviously it is possible to set title that contains cmd's standard delimiters but through TITLE command looks impossible to set a title that starts with a delimiter.
It is possible to create a new instance of CMD with such title:
start "==" cmd.exe
but not possible for the same instance.
It is possible also with .NET and Console.Title property but when it's called from batch file the tile lasts as long as the compiled exe runs:
#if (#X)==(#Y) #end /* JScript comment
#echo off
setlocal
for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d /o:-n "%SystemRoot%\Microsoft.NET\Framework\*jsc.exe"') do (
set "jsc=%%v"
)
if not defined jsc (
echo !!! Installation of .NET framework needed !!!
pause
exit /b 3
)
rem echo %jsc%
::if not exist "%~n0.exe" (
del /q /f %~n0.exe
call "%jsc%" /nologo /out:"%~n0.exe" "%~dpsfnx0"
::)
call %~n0.exe %*
endlocal & exit /b %errorlevel%
*/
import System;
var arguments:String[] = Environment.GetCommandLineArgs();
//Console.WriteLine(Console.Title);
if (arguments.length>0){
Console.Title=arguments[1];
}
//Console.WriteLine(Console.Title);
(Probably is possible through code injection)
Is there a windows 'native' way?
With Win7 x64, I can produce it when a linefeed is in front of the problematic characters.
#echo off
setlocal EnableDelayedExpansion
set LF=^
title !LF!,bc
In Win7 there seems no difference in the height or position of the title when LF is used.
Tested with
cmd /V:on
#for /L %n in (1 1 1111) do #(title !LF!=Hello& title +Hello)
But the current program is shown for the time it's running in the title, so the TITLE command itself produces a flicker.
I found another way:
title ^A,string
where ^A is produced by entering 0 1 while holding down the ALT-key ( <pressALT><0><1><releaseALT> ).
You can get it into a file with echo ^A>>batch.bat and then moving it to the correct position with notepad (it's not shown in notepad, like a whitespace; broader than a space, but not as broad as a TAB)
The LF is the answer to the question , but there's also a workaround. I think I've checked all the ASCII characters and looks like FS and GS characters are not displayed in title of command prompt (here's an example with FS):
#echo off
setlocal
::dbenham's hexprint was used here
::http://www.dostips.com/forum/viewtopic.php?p=23888
::Define a Linefeed variable
set LF=^
::above 2 blank lines are critical - do not remove.
::Create a FS variable
call :hexprint "0x1C" FS
title %FS%=;=
exit /b
:hexPrint string [rtnVar]
for /f eol^=^%LF%%LF%^ delims^= %%A in (
'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(%~1"'
) do if "%~2" neq "" (set %~2=%%A) else echo(%%A
exit /b
But this is not a 'perfect' solution as AppActivate or Tasklist command will detect the FS and GS characters (no such problem with LF).
Also SOH character can be used but it is displayed as space.
FS can be produced also with ctrl+]
GS with ctrl+\
SOH with ctrl+A
Obviously it is possible to set title that contains cmd's standard delimiters but through TITLE command looks impossible to set a title that starts with a delimiter.
It is possible to create a new instance of CMD with such title:
start "==" cmd.exe
but not possible for the same instance.
It is possible also with .NET and Console.Title property but when it's called from batch file the tile lasts as long as the compiled exe runs:
#if (#X)==(#Y) #end /* JScript comment
#echo off
setlocal
for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d /o:-n "%SystemRoot%\Microsoft.NET\Framework\*jsc.exe"') do (
set "jsc=%%v"
)
if not defined jsc (
echo !!! Installation of .NET framework needed !!!
pause
exit /b 3
)
rem echo %jsc%
::if not exist "%~n0.exe" (
del /q /f %~n0.exe
call "%jsc%" /nologo /out:"%~n0.exe" "%~dpsfnx0"
::)
call %~n0.exe %*
endlocal & exit /b %errorlevel%
*/
import System;
var arguments:String[] = Environment.GetCommandLineArgs();
//Console.WriteLine(Console.Title);
if (arguments.length>0){
Console.Title=arguments[1];
}
//Console.WriteLine(Console.Title);
(Probably is possible through code injection)
Is there a windows 'native' way?
With Win7 x64, I can produce it when a linefeed is in front of the problematic characters.
#echo off
setlocal EnableDelayedExpansion
set LF=^
title !LF!,bc
In Win7 there seems no difference in the height or position of the title when LF is used.
Tested with
cmd /V:on
#for /L %n in (1 1 1111) do #(title !LF!=Hello& title +Hello)
But the current program is shown for the time it's running in the title, so the TITLE command itself produces a flicker.
I found another way:
title ^A,string
where ^A is produced by entering 0 1 while holding down the ALT-key ( <pressALT><0><1><releaseALT> ).
You can get it into a file with echo ^A>>batch.bat and then moving it to the correct position with notepad (it's not shown in notepad, like a whitespace; broader than a space, but not as broad as a TAB)
The LF is the answer to the question , but there's also a workaround. I think I've checked all the ASCII characters and looks like FS and GS characters are not displayed in title of command prompt (here's an example with FS):
#echo off
setlocal
::dbenham's hexprint was used here
::http://www.dostips.com/forum/viewtopic.php?p=23888
::Define a Linefeed variable
set LF=^
::above 2 blank lines are critical - do not remove.
::Create a FS variable
call :hexprint "0x1C" FS
title %FS%=;=
exit /b
:hexPrint string [rtnVar]
for /f eol^=^%LF%%LF%^ delims^= %%A in (
'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(%~1"'
) do if "%~2" neq "" (set %~2=%%A) else echo(%%A
exit /b
But this is not a 'perfect' solution as AppActivate or Tasklist command will detect the FS and GS characters (no such problem with LF).
Also SOH character can be used but it is displayed as space.
FS can be produced also with ctrl+]
GS with ctrl+\
SOH with ctrl+A
This is probably impossible, but I have a loop that displays a animated logo by using TYPE to type logo_(framenumber).txt and the framenumber is determined by a loop:
:s
if %m%==379 set m=0
cls
TYPE Logo_%m%.txt
set /a m=%m%+1
goto s
I wanted to be able to use a set /p option and without disturbing/stopping the loop so the animation plays while a user is typing in the set /p input. I think there is a way to do it with FOR but I'm not sure how. Any ideas? Thanks.
Although this topic is somewhat old, I just discovered it. This is a pure Batch file solution that works pretty well:
EDIT: I slightly modified the code in order to made it simpler.
#echo off
setlocal EnableDelayedExpansion
if "%1" equ "Animate" goto %1
for /F %%a in ('echo prompt $H ^| cmd') do set "BS=%%a"
for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"
cd . > input.txt
start "" /B "%~F0" Animate
set "input="
:nextKey
set "key="
for /F "delims=" %%K in ('xcopy /W "%~F0" "%~F0" 2^>NUL') do if not defined key set "key=%%K"
if "!key:~-1!" equ "!CR!" goto endInput
if "!key:~-1!" equ "!BS!" (
if defined input set "input=%input:~0,-1%"
) else (
set "input=%input%!key:~-1!"
)
set /P "=%input%" > input.txt < NUL
goto nextKey
:endInput
del input.txt
echo/
echo/
echo Input read: "%input%"
goto :EOF
:Animate
set "banner= Enter your name please "
set m=0
:loop
if not exist input.txt exit
set /A m=(m+1)%%51
cls
echo/
echo/ !banner:~%m%,31!
echo/
echo/
if exist input.txt (type input.txt) else exit
ping -n 1 -w 300 localhost > NUL
ping -n 1 -w 300 localhost > NUL
ping -n 1 -w 300 localhost > NUL
goto loop
In this solution the animated "logo" is replaced by a banner, but the method to display a series of files is practically the same.
EDIT: This is possible in batch. See Aacini's answer.
This is not possible with batch files. Batch commands are single-threaded. To run two things simultaneously requires two instances of cmd.exe. But the console subsystem only allows one program to own the console at a time, so if the second instance of cmd is attached to the same console, one of them must be blocked.
It is possible to do something like this with a win32 executable which uses WriteConsoleOutput to modify the characters on the console screen. If you do that, you are no longer limited to just dumping text files, but the downside is that it's a lot more work than calling type in batch.
Sorry I know similar things have been asked here but basically I'm trying to read a text file within a batch script and evaluate what has been written to the file.
The job is a print job that sends a file to a printer, I have it echoing the output from the command to a log file. I then want to read in what the output was and if there was an error I will then send an email so we know when things stop working.
It always appends to the end of the file so I know if there's an error the 4th from last line will begin with "Error:". So my question is how can I read that in to a variable so I can perform an IF statement. I've got the emailing part sorted it's just reading from the file that I'm struggling with.
Any help would be much appreciated. Here's an example of the content of the file when there's an error:
----
C:\XG1\DGS01\prints\000000398200001.XG1
19/03/2013
15:02
1 file(s) copied.
Error: print server unreachable or specified printer does not exist.
1 file(s) moved.
It leaves one blank line at the end of the file so I'm going with the last line minus 4.
Thank you
as a line in your batch file:
for /f "tokens=1*delims=:" %%i in (thenameofyourfile) do if "%%i"=="Error" set message=%%j
echo message was "%message%"
Actually, that will report if ANY lines are in the format you describe.
#ECHO OFF
SETLOCAL
(SET message=)
FOR /f "tokens=1,2*delims=[]:" %%i IN (
' TYPE thenameofyourfile^|find /n /v "" '
) DO (
SET lastline=%%i
IF "%%j"=="Error" SET errorline=%%i&SET message=%%k
)
SET /a target=%errorline% + 3
IF %target% neq %lastline% (SET message=)
IF DEFINED message ECHO error found %message%
should get the line ONLY if it's the fourth last line in the file - the "+ 3" being the line-count required (well, minus 1)
BUT - remember that if this is, as it seems, a log file that it's possible (I'd imagine) that further entries may appear AFTER the error (for further jobs) so the target for the Line beginning "Error:" may not be the fourth-last...
OTOH, using the line(s) I first posted, once an "Error:..." line appears, it will be detected EVERY time - you'd need to reset the logfile in your mail-send procedure (save existing & recreate empty?)
Solution without any loop (does test if any error):
#findstr Error: printer.log >nul 2>&1
#if %errorlevel% equ 0 echo Send email now!
And this code does only test the fourth line before the last line for an error:
#echo off &setlocal enabledelayedexpansion
for /f %%i in ('findstr /n "^" printer.log') do (
set line4=!line3!&set line3=!line2!&set line2=!line1!&set line1=%%i)
echo %line4%|findstr Error: >nul 2>&1
if %errorlevel% equ 0 echo Send email now!
If you want to start reading the fourth line from the bottom of your log file, you can count the number of lines in your log file, subtract 4, then more +%count% to get the tail of the log.
#echo off
setlocal
set logfile=printerlog.log
for /f "tokens=2 delims=:" %%I in ('find /c /v "" "%logfile%"') do set lines=%%I
set /a "tail4=%lines% - 4"
for /f %%I in ('more +%tail4% "%logfile%" ^| find /i "Error:"') do (
rem do your email voodoo here
)
If your log file will exceed 65,535 lines, I recommend looping through the file using JScript or VBScript instead of a batch loop. Skipping that many lines with more will cause more to crash, so you'd have to loop line-by-line and increment a counter. However, batch for loops are terribly slow.
Here's the same script that replaces more with JScript-ish file reading.
#if (#a==#b) #end /*
:: batch portion
#echo off
setlocal
set logfile=printerlog.log
for /f "tokens=2 delims=:" %%I in ('find /c /v "" "%logfile%"') do set lines=%%I
set /a "tail4=%lines% - 4"
for /f "delims=" %%I in ('cscript /nologo /e:jscript "%~f0" %tail4% "%logfile%" ^| find /i "Error:"') do (
rem do your email voodoo here
rem %%I contains the matching line.
rem After one match, stop processing further
goto :EOF
)
goto :EOF
::JScript portion */
var i=0, fso = new ActiveXObject("Scripting.FileSystemObject").OpenTextFile(WSH.Arguments(1),1);
while (!fso.AtEndOfStream) {
if (i++ < WSH.Arguments(0)) fso.SkipLine();
else WSH.Echo(fso.ReadLine());
}
fso.Close();
I believe the fastest and least resource-intensive way to check for errors is with GNU tail. If you can, get the .zip binaries and put tail.exe in your path or where your batch script can access it. Then:
#echo off
setlocal
tail -n 4 printerlog.log | find /i "Error:" >NUL && (
echo Error found. Sending email.
rem do email stuff
)
Or if you wish to capture the text of the error for your email:
#echo off
setlocal
for /f "delims=" %%I in ('tail -n 4 printerlog.log ^| find /i "Error:"') do (
echo Error found: %%I
rem do email stuff
goto :EOF
)
tail is much more efficient than all these other methods of counting the number of lines in the log file and looping through the log file line-by-line, whether looping in batch or in JScript.
A while ago I made a function that you can call from the command prompt or any batch file (it was just for fun, I don't see how it could be useful). It basically just makes your (Microsoft) computer speak whatever you wrote in as the parameter.
I recently got some inspiration to add a switch to it where it would read the contents of a file. My standalone script worked, but when I added it to my function, it didn't work as I would have liked.
Here's the code:
#echo off & setlocal enabledelayedexpansion
if "%~1"=="/?" (
echo.
echo TALK "Text" [Parameters]
echo.
echo Text - The phrase you want to be spoken.
echo.
echo [Parameters]:
echo /f - Read the contents of a file. "Text" changes to the file path.
echo.
endlocal
exit /b
)
if "%~2 X" equ "/f X" (
if not exist %~1 (
echo File does not exist or cannot be found.
endlocal
exit /b
)
set cont=
for /f "delims=" %%i in (%~1) do set cont=!cont! %%i
:b
echo Set a = Wscript.CreateObject("SAPI.SpVoice") > "Talk.vbs"
echo a.speak "%cont%" >> "Talk.vbs"
start /WAIT Talk.vbs
del Talk.vbs
endlocal
exit /b
)
set text=%~1
echo set speech = Wscript.CreateObject("SAPI.spVoice") > "talk.vbs"
echo speech.speak "%text%" >> "talk.vbs"
start /WAIT talk.vbs
del Talk.vbs
endlocal
exit /b
Unfortunately I don't have working function code (before I added the /f switch).
This is a last resort for me as I've edited it heavily and scoured the code for any give away as to what the problem might be.
Another bad thing is that I didn't take note of what I changed, so I can't exactly tell you what I've tried. I can tell you what the outputs are though.
The first time I tried, it gave the output The syntax of the command is incorrect.
It's now at the point where the original function (just converting text to speech) doesn't work anymore. The contents of the file Talk.vbs (which was made during the process) is a.speak "".
I'll keep updating my attempts, but knowing me it's something really simple that I've overlooked.
--EDIT--
At the suggestion of someone, I put carats before the square brackets in the syntax section. Nothing changed.
Along with escaping the parenthesis you also had to surround if exist %~1 in quotes in case of a argument of "some words I want it to say". Also cleaned it up a bit. Code at the bottom, but first an explanation.
If you looked at talk.vbs before it was deleted you would see this:
a.speak "!cont! contents of the file here"
This is because of this code:
for /f "delims=" %%i in (%~1) do set cont=!cont! %%i
:b
echo Set a = Wscript.CreateObject("SAPI.SpVoice") > "Talk.vbs"
If you turned echo on and watched the code you would see the last unescaped ) was taking the contents of the for loop and including it in the redirect.
Corrected and cleaned code:
#echo off & setlocal enabledelayedexpansion
if "%~1"=="/?" (
echo.
echo TALK "Text" [Parameters]
echo.
echo Text - The phrase you want to be spoken.
echo.
echo [Parameters]:
echo /f - Read the contents of a file. "Text" changes to the file path.
echo.
endlocal
exit /b
)
set text=
if [%2]==[/f] (
if exist "%~1" (
for /f "usebackq delims=" %%i in (%1) do set text=!text! %%i
) else (
endlocal
exit /B
)
)
if [%2]==[] set text=%~1
echo set speech = Wscript.CreateObject^("SAPI.spVoice"^) > "talk.vbs"
echo speech.speak "%text%" >> "talk.vbs"
cscript //NoLogo //B talk.vbs
del Talk.vbs
endlocal
exit /b
Edit: fixed the for statement pointed out by Andriy M
In your echo statements that contain parentheses, try escaping the parentheses with carats. I suspect especially the echo within the if statement is partially getting evaluated literally.
One other minor suggestion, I would also replace
start /WAIT Talk.vbs
with
cscript /nologo Talk.vbs
It's not that I think the start /wait is causing the error, but it does cause a second console window to appear temporarily for no good reason -- or it will whenever your script executes that far, anyway.
I made a few other suggested changes here, such as eliminating the need for a /f switch. If "%1" is the name of a file that exists, read it. Otherwise, treat it as text to read. And instead of having a separate subroutine for reading a file versus getting text from input, all that needs to happen is a variable has a different value.
#echo off & setlocal enabledelayedexpansion
if "%1"=="/?" ( goto usage )
if "%1"=="" ( goto usage )
if "%1"=="--help" ( goto usage )
if exist "%1" (
set txt=
for /f "usebackq tokens=*" %%i in (%1) do set txt=!txt! %%i
) else (
set txt=%1
)
echo Set a = Wscript.CreateObject^("SAPI.SpVoice"^) > "talk.vbs"
echo a.speak "%txt%" >> "talk.vbs"
cscript /nologo talk.vbs
del talk.vbs
endlocal
goto :EOF
:usage
echo.
echo TALK ["text"^|filename]
echo.
echo talk filename -- speaks the contents of filename
echo talk "text" -- speaks the supplied text
endlocal
goto :EOF