batch file setting system time with GPS - batch-file

My GPS device sends on COM port the NMEA infos.
I want to extract the time info and set the system time.
In a batch I wrote:
type COM2 | find "GPRMC"
brings me the required info - but continously.
Example (first 3 lines):
$GPRMC,100211.279,V,4816.1496,N,01623.0965,E,0.00,0.00,280316,,,N*7A
$GPRMC,100212.279,V,4816.1496,N,01623.0965,E,0.00,0.00,280316,,,N*79
$GPRMC,100213.279,V,4816.1496,N,01623.0965,E,0.00,0.00,280316,,,N*78
So the next batch command is never executed, because the GPS device is sending as long as it's on.
I want to extract the second field, as in example, it is 100211 and this is 10:02:11 UTC time.
greetings

You can use this:
#echo off
for /f "tokens=2 delims=,." %%a in ('type COM2 ^| find "GPRMC"') do (
set "comTime=%%a"
goto :break
)
:break
set "comTime=%comTime:~0,2%:%comTime:~2,2%:%comTime:~4,2%
echo %comTime%
pause
This sets the time of the first line in the file to the variable comTime in the format hh:mm:ss
NOTE
This removes the .279 part of the time, to keep that part change delims=,. to delims=,

Next code snippet would take line(s) from an endless command output.
Avoid | pipe to find or findstr (read more, see Pipes and CMD.exe paragraph).
Think about delay(s) caused by …
… involved timeout commands,
… handling temporary file and
… process handling (cmd, taskkill, for).
Tested using endless ping localhost -4 -t:
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "mycommand=type COM2"
set "mytestval=$GPRMC"
rem next two lines are merely for my tests: remove them both
set "mycommand=ping localhost -4 -t"
set "mytestval=Reply"
start "bubu36260096" cmd /C ^>"%temp%\aux36260096.txt" %mycommand%
>NUL timeout /T 3 /NOBREAK
>NUL taskkill /T /F /FI "IMAGENAME eq cmd.exe" /FI "WINDOWTITLE eq bubu36260096"
>NUL timeout /T 1 /NOBREAK
rem v--- skip as many lines as you need
for /f "usebackq skip=1 tokens=1,2 delims=, " %%a in ("%temp%\aux36260096.txt") do (
rem ^--- remove this blank space in above line
if "%%a"=="%mytestval%" (
set "comTime=%%b"
goto :break
)
)
:break
rem next line for debugging purposes only
type "%temp%\aux36260096.txt"
rem set "comTime=%comTime:~0,2%:%comTime:~2,2%:%comTime:~4,2%
echo comTime ... "%comTime%"
pause

Related

How to escape spaces while Set a parameter in Bat file

I'm trying to do this:
#echo off
Set ^"Processes=Sample.exe ^
The Sample Program.exe^"
But The Sample Program.exe acting as three separate files The Sample and Program.exe.
What is the procedure to escape the spaces?
Full code:
for %%a in (%Processes%) Do (
for /f %%b in ('tasklist /NH /FI "imagename eq %%a"') Do (
if [%%b]==[%%a] (
echo %%b is running
Color 0C
echo Killing %%b ...
Taskkill /f /im "%%b"
) else (
Color 0A
echo %%a is not running
)
)
)
pause & exit
Something along this line?
for /F "tokens=1* delims=," %%b
A simple demonstration code for what I think you want to achieve is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set Processes="cmd.exe Windows Command Processor" "cscript.exe Console Based Script Host"
for %%G in (%Processes%) do for /F "eol=| tokens=1*" %%H in (%%G) do (
echo File name is: %%H
echo Process name: %%I
)
endlocal
The output of this batch file on execution is:
File name is: cmd.exe
Process name: Windows Command Processor
File name is: cscript.exe
Process name: Console Based Script Host
So the environment variable Processes is defined with multiple strings enclosed in double quotes. Each string has first the file name of an executable without path and separated by a space (could be also a different character) the process name or whatever is needed to be associated with the executable file name which should not contain ? or *.
The outer FOR loop assigns one after the other a string enclosed in double quotes to the specified loop variable G.
The inner FOR loop splits up the string assigned currently to loop variable G into two substrings and assigns the first normal space/horizontal tab delimited string to specified loop variable H and everything else after one or more spaces/tabs to next but one loop variable I.
The executable file name assigned to H and the associated string assigned to I can be used for whatever purpose in the command block of the inner FOR loop.
This method applied to what the batch file should do:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set Processes="notepad.exe Windows Notpad" "cscript.exe Console Based Script Host"
for %%G in (%Processes%) do for /F "eol=| tokens=1*" %%H in (%%G) do (
%SystemRoot%\System32\tasklist.exe /NH /FI "imagename eq %%H" 2>nul | %SystemRoot%\System32\find.exe /I "%%H" >nul
if not errorlevel 1 (
echo %%I is running.
color 0C
echo Terminating %%I ...
%SystemRoot%\System32\taskkill.exe /IM "%%H" >nul
) else (
color 0A
echo %%I is not running.
)
)
color
endlocal
The option /F to force a brutal kill of the running process by the operating system is removed from this code. The most applications gracefully terminate itself on TASKKILL sending the WM_CLOSE message to the running application.
Please note that closing all instances of script interpreters like cmd.exe, cscript.exe, wscript.exe, powershell.exe as done by the TASKKILL with just image name passed as argument is in general not good. For example the cmd.exe instance currently processing the batch file would be terminated also on using this batch code to terminate a cmd.exe running with a different console window parallel.
Example for the unusual cases of executable file names with one or more spaces with using | as delimiter between file name and associated string which no file name can contain ever:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set Processes="notepad.exe|Windows Notpad" "cscript.exe|Console Based Script Host" "show time.exe|Show Time Information"
for %%G in (%Processes%) do for /F "eol=| tokens=1* delims=|" %%H in (%%G) do (
%SystemRoot%\System32\tasklist.exe /NH /FI "imagename eq %%H" 2>nul | %SystemRoot%\System32\find.exe /I "%%H" >nul
if not errorlevel 1 (
echo %%I is running.
color 0C
echo Terminating %%I ...
%SystemRoot%\System32\taskkill.exe /IM "%%H" >nul
) else (
color 0A
echo %%I is not running.
)
)
color
endlocal
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
endlocal /?
find /?
for /?
setlocal /?
taskkill /?
tasklist /?
Example based upon my comment:
Set ^"Processes="Sample.exe"^
"The Sample Program.exe"^"
For %%G In (%Processes%) Do Echo %%G
Pause
Obviously when using %%G in your /Filter, you'd just remove those doublequotes, i.e. %SystemRoot%\System32\tasklist.exe /NH /Fi "ImageName Eq %%~G"
Please note that the leading space before any line which begins with a doublequote is required

Scope problems in batch file with nested loops

I have an SSIS job running in a batch file that executes asynchronously.
I need to know when the SSIS job is done outputting a bunch of PDF and XLS files.
The files appear in two directories, PDFs first XLS following.
I chose to write a second batch file that will wait a bit after the SSIS job exits, then check to see that the last file written in the directory has been there for 3 minutes, which, after observation, is an ample interval for the job to write a file.
The problem is: the outer loop is never run if the inner loop iterates more than once, which seems to indicate that the second time MYPATH is declared, the value of n is foobarred, but this cannot be true because the script is returning 1, rather than crapping out when Arr[badval] is checked.
#ECHO OFF
REM SSIS process is asyncronous, and executes in background. Problematic for
REM DAG, which relies on exit code to understand process.
REM Check for "last file written" in output directories every N seconds.
REM It's a good bet we are done when they match.
#setlocal enabledelayedexpansion
REM "sleep" wait for non-existent command to complete
waitfor ragnarok /t 180>NUL 2>&1
set Arr[0]=E:\pdf_output
set Arr[1]=E:\xls_output
for /l %%n in (0,1,2) do (
if defined Arr[%%n] (
REM set value for path within loop or scope will bite you
set MYPATH=!Arr[%%n]!
echo Checking file ages in !MYPATH!.
) else (
echo Done
EXIT /B 0
)
:while1
REM don't put a blank line here, it throws a syntax error
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE1=%%I
REM "sleep" use ping for delay, since waitfor will break loop
arp -s 192.168.1.254 >nul
ipconfig /flushdns >nul
ping localhost -n 180 >nul
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE2=%%I
if NOT "!FILE1!" == "!FILE2!" (
goto :while1
)
)
endlocal
REM Something is wrong, return 1 to stop DAG and invite inspection.
exit /B 1
Breaking the while loop into a subroutine as #aschipfl suggested seems to have done the trick:
#ECHO OFF
REM SSIS process is asyncronous, and executes in background. Problematic for
REM DAG, which relies on exit code to understand process.
REM Check for "last file written" in output directories every N seconds.
REM It's a good bet we are done when they match.
#setlocal enabledelayedexpansion
REM "sleep" wait for non-existent command to complete
waitfor ragnarok /t 120>NUL 2>&1
set Arr[0]=E:\pdf_output
set Arr[1]=E:\xls_output
for /l %%n in (0,1,2) do (
if defined Arr[%%n] (
REM set value for path within loop or scope will bite you
set MYPATH=!Arr[%%n]!
echo Checking file ages in !MYPATH!.
CALL :checkfiles
) else (
echo Done
goto :NormalExit
)
)
:checkfiles
:while1
REM don't put a blank line here, it throws a syntax error
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE1=%%I
REM "sleep" use ping for delay, since waitfor will break loop
arp -s 192.168.1.254 >nul
ipconfig /flushdns >nul
ping localhost -n 120 >nul
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE2=%%I
if NOT "!FILE1!" == "!FILE2!" (
goto :while1
)
endlocal
:NormalExit
exit /B 0
REM Something is wrong, return 1 to stop DAG and invite inspection.
exit /B 1

Pinging Multiple Computers IF Statement

I'm trying to create a little batch file that checks multiple PCs read from a text file. For any PCs it finds are pingable, it writes a line in a "results" text file saying so. Here's what I've got:
#Echo off
set file=C:\logs\registercheck.txt
date /t >%file%
FOR /F %%I IN (C:\work\regnames.txt) DO (ping /n 1 %%I | ping /n 1 %%I | IF errorlevel 1 goto :nextreg | echo %%I is still on and has not been powered off! >>%file% | :nextreg)
PAUSE
So...when I run the file, I get multiple lines of "goto was unexpected at this time" and the only thing written in my output text file is the date. What am I doing wrong?
Thank you!
#Echo off
setlocal enableextensions disabledelayedexpansion
set "logFile=C:\logs\registercheck.txt"
set "inputFile=C:\work\regnames.txt"
>>"%logFile%" date /t
for /f "usebackq delims=" %%i in ("%inputFile%") do (
ping -n 1 %%i >nul 2>nul
if not errorlevel 1 (
>>"%logFile%" echo(%%i is still on and has not been powered off!
)
)
You have two errors.
The first is that to put all the commands in a single line, the separator is not the pipe character (|) but the ampersand (&)
The second is that inside the do code block of the for command, if one goto is executed, the for command is finished, independently of where the label is placed. And labels inside for code blocks usually generate errors (depends of its position).
If instead of the previous code, you want a single line loop, it can be written as
for /f "usebackq delims=" %%i in ("%inputFile%") do ( ping -n 1 %%i >nul 2>nul & if not errorlevel 1 >>"%logFile%" echo(%%i is still on and has not been powered off! )
or
for /f "usebackq delims=" %%i in ("%inputFile%") do ( ping -n 1 %%i >nul 2>nul && >>"%logFile%" echo(%%i is still on and has not been powered off! )
that makes use of the && construct. It is intended as a shortcut for the if not errorlevel 1 .... If the command at the left of the && does not raise an errorlevel, then the command on the right side is executed.
This for the batch sintax. Now the ping. There is a difference in how ping command behaves depending of the ip version. It is not the same to ping an ipv4 address than to ping an ipv6 address. If needed you can grab from here a subrotine to handle the differences.

Read the first word on a specific line from a text file using batch script then feed into an if statement

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.

Slow processing a for loop that utilizes findstr

I've got a somewhat weird case, where a for-loop is incredibly slow when I use findstr as the string for DO.
Its worth mentioning that the file (old-file.xml) that I'm processing contains about 200 000 lines.
This part is blazing fast, but can be rendered slower if I remove | find /c ":"
rem find total number of lines in xml-file
findstr /n ^^ old-file.xml | find /c ":" > "temp-count.txt"
set /p lines=< "temp-count.txt"
The code which is slow looks like this and I can't use the pipe trick above. It seems like the slow part is the for itself, as i'm not seeing any progress in the title bar until after 10 min.
setlocal DisableDelayedExpansion
rem start replacing wrong dates with correct date
for /f "usebackq Tokens=1* Delims=:" %%i in (`"findstr /n ^^ old-file.xml"`) do (
rem cache the value of each line in a variable
set read-line=%%j
set line=%%i
rem restore delayed expansion
setlocal EnableDelayedExpansion
rem write progress in title bar
title Processing line: !line!/%lines%
rem remove trailing line number
rem set read-line=!read-line:*:=!
for /f "usebackq" %%i in ("%tmpfile%") do (
rem replace all wrong dates with correct dates
set read-line=!read-line:%%i=%correctdate%!
)
rem write results to new file
echo(!read-line!>>"Updated-file.xml"
rem end local
endlocal
)
EDIT:
Further investigation showed me that using this single line that should display the current line number being looped takes about 10 minutes on my 8MB file of 200 000 lines. That's just for getting it to start displaying the lines.
for /f "usebackq Tokens=1* Delims=:" %%i in (`"findstr /n ^^ old-file.xml"`) do echo %%i
So it seems like findstr is writing screen output hidden for the user, but visible for the for-loop. How can I prevent that from happening while still getting the same results?
EDIT 2: Solution
The solution as proposed by Aacini and finally revised by me.
This is a snippet from a much bigger script. Wrong dates are retrieved in another loop. And total number of lines are also retrieved from another loop.
setlocal enabledelayedexpansion
rem this part is for snippet only, dates are generated from another loop in final script
echo 2069-04-29 > dates-tmp.txt
echo 2069-04-30 >> dates-tmp.txt
findstr /n ^^ Super-Large-File.xml > out.tmp
set tmpfile=dates-tmp.txt
set correctdate=2011-11-25
set wrong-dates=
rem hardcoded total number of lines
set lines=186442
for /F %%i in (%tmpfile%) do (
set wrong-dates=!wrong-dates! %%i
)
rem process each line in out.tmp and loop them through :ProcessLines
call :ProcessLines < out.tmp
rem when finished with above call for each line in out.tmp, goto exit
goto ProcessLinesEnd
:ProcessLines
for /L %%l in (1,1,%lines%) do (
set /P read-line=
rem write progress in title bar
title Processing line: %%l/%lines%
for %%i in (%wrong-dates%) do (
rem replace all wrong dates with correct dates
set read-line=!read-line:%%i=%correctdate%!
)
rem write results to new file
echo(!read-line:*:=!>>"out2.tmp"
)
rem end here and continue below
goto :eof
:ProcessLinesEnd
echo this should not be printed until call has ended
:exit
exit /b
Two points here:
1- The setlocal EnableDelayedExpansion command is executed with every line of the file. This means that about 200000 times the complete environment must be copied to a new local memory area. This may cause several problems.
2- I suggest you to start with the most basic part. How much time takes the findstr to execute? Run findstr /n ^^ old-file.xml alone and check this before trying to fix any other part. If this process is fast, then add a single step to it and test again until you discover the cause of the slow down. I suggest you not use pipes nor for /f over the execution of findstr, but over the file generated by a previous redirection.
EDIT A faster solution
There is another way to do this. You may pipe findstr output into a Batch subroutine, so the lines can be read with SET /P command. This method allows to process the lines entirely via delayed expansions and not via the command-line susbtitution of FOR /F, so the pair of setlocal EnableDelayedExpansion and endlocal commands are no longer necessary. However, if you still want to display the line number it is necessary to calculate it again.
Also, it is faster to load the wrong dates in a variable instead of process the %tmpfile% with every line of the big file.
setlocal EnableDelayedExpansion
rem load wrong dates from tmpfile
set wrong-dates=
for /F %%i in (%tmpfile%) do (
set wrong-dates=!wrong-dates! %%i
)
echo creating findstr output, please wait...
findstr /n ^^ old-file.xml > findstr.txt
echo :EOF>> findstr.txt
rem start replacing wrong dates with correct date
call :ProcessLines < findstr.txt
goto :eof
.
:ProcessLines
set line=0
:read-next-line
set /P read-line=
rem check if the input file ends
if !read-line! == :EOF goto :eof
rem write progress in title bar
set /A line+=1
title Processing line: %line%/%lines%
for %%i in (%wrong-dates%) do (
rem replace all wrong dates with correct dates
set read-line=!read-line:%%i=%correctdate%!
)
rem write results to new file
echo(!read-line:*:=!>>"Updated-file.xml"
rem go back for next line
goto read-next-line
SECOND EDIT An even faster modification
Previous method may be slighlty speeded up if the loop is achieved via for /L command instead of via a goto.
:ProcessLines
for /L %%l in (1,1,%lines%) do (
set /P read-line=
rem write progress in title bar
title Processing line: %%l/%lines%
for %%i in (%wrong-dates%) do (
rem replace all wrong dates with correct dates
set read-line=!read-line:%%i=%correctdate%!
)
rem write results to new file
echo(!read-line:*:=!>>"Updated-file.xml"
)
This modification also omit the :EOF comparison and the calculation of line number, so the time gain may be significative after repeated it 200000 times. If you use this method, don't forget to delete the echo :EOF>> findstr.txt line in first part.
A FOR /F expression will always executed/read/evaluated complete before the inner loop starts.
You can try it with
(
echo line1
echo line2
) > myFile.txt
FOR /F "delims=" %%a in (myFile.txt) DO (
echo %%a
del myFile.txt 2> nul >nul
)
It will display
line1
line2
In your case the complete ('"findstr /n ^^ old-file.xml"') will executed and cached before the loop can start
EDIT: Added Solution
I measured with a file ~20MB with 370.000 lines
type testFile.txt > nul
findstr /n ^^ testFile.txt > nul
for /F "delims=" %%a in (testFile.txt) do (
rem Nothing
)
for /f "usebackq delims=" %%a in (`"findstr /n ^^ testFile.txt"`) do ...
findstr /n ^^ testFile.txt > out.tmp
type_nul ~10000ms
findstr_nul ~30000ms
for_file ~ 1600ms
for_findstr cancled after 10 minutes
findstr_tmp ~ 500ms !!!
I would recommend to use a temporary file, it's extremly fast.
findstr /n ^^ myFile.txt > out.tmp
set lineNr=0
(
for /f "usebackq delims=" %%a in ("out.tmp") do (
set /a lineNr+=1
set "num_line=%%a"
setlocal EnableDelayedExpansion
set "line=!num_line:*:=!"
echo(!line!
endlocal
)
) > out2.tmp
Btw. Your for/F splitting can fail, if the original line begins with a colon
for /f "usebackq Tokens=1* Delims=:"
Sample: :ThisIsALabel
:ThisIsALabel
Findstr /n prepends a line number
17::ThisIsALabel
the delims=: will split the first token and handles all colons as only one seperator
ThisIsALabel

Resources