CMD return specific lines from piped input - cmd

Bored in class trying to figure this out.
On windows command prompt:
ipconfig /all return all the loopback, tunnel, etc.
if I run ipconfig /all | find /n "Internal" it will return [11]Ethernet Adapter Internal. What I want to do is substring the 11 off the beginning and then pipe this to something else which will allow me to return lines 11-19 or whatever. Can you do this in a single line similar to jquery and chaining?

It is simple to parse out the 11 using a FOR /F loop. But making use of that value is difficult in a one liner from the command line.
Here is the best I could come up with for a one liner to run from the command line.
set "stop="&for /f "delims=[]" %N in ('ipconfig /all^|find /n "Internal"') do #for /f "tokens=1* delims=:" %A in ('ipconfig /all^|findstr /n "^"') do #if %A geq %N if not defined stop echo(%B&>nul 2>nul set /a 1/(%A-%N-8)||set stop=1
The shortest one liner I could think of uses a different strategy.
set n=&for /f "delims=" %A in ('ipconfig/all') do #if defined n (set/a"1/n,n-=1">nul 2>&1&&echo %A) else echo %A|find "Internal"&&set n=8
But the above solution ignores blank lines.
If you want to preserve blank lines then the following works:
set n=&for /f "tokens=1* delims=:" %A in ('ipconfig/all^|findstr/n "^"') do #if defined n (set/a"1/n,n-=1">nul 2>&1&&echo(%B) else echo %B|find "Internal"&&set n=8
EDIT
I shortened and simplified the logic of the 2nd and 3rd solutions a bit.
I'll try to explain the 2nd option:
I first explicitly undefine a counter and then use FOR to read the output of the IPCONFIG/ALL commmand. Now begins the fun part within the DO clause.
At first the counter is undefined so the ELSE clause is executed. I pipe the current line to FIND looking for "Internal". If the string is not found then nothing happens and we progress to the next line. But if it is found then the line is printed. Then the code after && is executed since the string was found. There I set the counter to 8 because we want the next 8 lines printed.
The counter is defined for the remainder of the lines, so we now switch to the first part of the IF statement. I use SET /A to do some math. First I divide 1 by the current value of the counter. IF the counter is 0 then an error is raised and the rest of the statement is ignored. If the counter is not 0 then I next decrement the counter by 1. I redirect both stdout and stderr to nul because we don't want to see any messages from the SET /A command. If the SET command was successful, then I echo the line. Since I initially set the counter to 8 when the string was found, up to 8 lines will be printed before the counter reaches 0.

Related

How to reverse string list in batch

I have a problem with reversing string list in a batch script. Let say I have a list L=string1,string2,string3 I would like to obtain reversed list L=string3,string2,string1. Any ideas??
You may also use this shorter/simpler approach:
#echo off
setlocal EnableDelayedExpansion
set "L=string1,string2,string3"
echo Input =%L%
set "revL="
set "str=%L:,=" & set "revL=,!str!!revL!" & set "str=%"
set "revL=%str%%revL%"
echo Output=%revL%
This method use the same procedure of the other answers, but in less lines. If you want to know what happens here, remove the #echo off line and run it. ;)
Without knowing what your input looks like, this might be a first attempt:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET var=abc,def,ghi
SET rev=
:LOOP
IF NOT "!var!"=="" (
FOR /F "delims=, tokens=1,*" %%F IN ("!var!") DO (
SET rev=%%F,!rev!
SET var=%%G
)
) ELSE (
SET rev=!rev:~0,-1!
GOTO ENDLOOP
)
GOTO LOOP
:ENDLOOP
ECHO reversed list is: !rev!
EDIT: As requested, here is an explanation how it works:
var is your starting list of strings separated by commas.
rev will be the reversed string. At the beginning this string is empty.
Now let's take a look at the loop:
In each iteration, we are separating our string into two parts: %%F and %%G. %%F will be everything before the first comma and %%G will be the rest of the string: FOR /F "delims=, tokens=1,*" %%F IN ("!var!"). delims=, means that we are using comma as delimiter. tokens=1,* means that the first found substring will be stored in %%F while the rest will be stored in %%G (%%F is defined for the first token so Windows command interpreter will put every token afterwards in G, H, I, and so on - as we are using *, everything will land in %%G). Finally, we take the first token of our string (%%F) and append ,!rev! to it. Then we set the remaining string list to everything behind the first comma (%%G).
In the first iteration, this loop does the following (pseudo code):
var=abc,def,ghi
rev=
split the string into %%F=abc and %%G=def,ghi
set rev to %%F,rev //means abc,
set var to var but without the first token //means def,ghi
In the second iteration:
var=def,ghi
rev=abc,
split the string into %%F=def and %%G=ghi
set rev to %%F,rev //means def,abc
set var to var but without the first token //means ghi
In the third iteration:
var=ghi
rev=def,abc
split the string into %%F=ghi %%G=
set rev to %%F,rev //means ghi,def,abc,
set var to var but without the first token //means empty string
Now, after jumping back to :LOOP, the if condition is no longer fulfilled as !var! has shrunk from formerly abc,def,ghi to now an empty string. So IF NOT !var!=="" becomes false and we are jumping to the ELSE clause.
There is one problem left: as we are constructing our reversed string by pre-appending the first token from the original list AND a comma, we will end up with a comma at the end of the reversed string list: ghi,def,abc,
SET rev=!rev:~0,-1! fixes this. It takes a "substring" from our string, starting at index 0 and finishing at "end-1". So this line simply removes the last , at the end of our string. Then we are jumping to :ENDLOOP.
Here is a batch file code assuming L=string1,string2,string3 is assigned to an environment variable:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ListLine=L=string1,string2,string3"
for /F "tokens=1* delims==" %%I in ("%ListLine%") do (
set "LineBegin=%%I"
set "ListItems=%%J"
)
set "ReversedItems="
for %%I in (%ListItems%) do call set "ReversedItems=%%I,%%ReversedItems%%"
set "ListLine=%LineBegin%=%ReversedItems:~0,-1%"
echo %ListLine%
endlocal
Windows command interpreter interprets a comma in list of strings in a simple FOR loop like a space character as it can be seen on running this batch file without #echo off from within a command prompt window. Therefore the second FOR loop runs first with string1 assigned to loop variable I, second with string2 and third with string3.
Command CALL is used to do a double processing of the command SET to avoid the requirement to use delayed environment variable expansion as explained by help for command SET output on running set /? in a command prompt window.
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.
call /?
echo /?
endlocal /?
for /?
set /?
setlocal /?
Aacini, definitely has the fastest code out of all of the answers. This is some longer code that uses a similar SET trick.
#echo off
setlocal EnableDelayedExpansion
set i=1
set "x=abc,def,ghi"
set "x!i!=%x:,=" & set /A i+=1 & set "x!i!=%"
FOR /L %%G IN (%i%,-1,1) DO (
IF "%i%"=="%%G" (
set "reverse=!x%%G!"
) ELSE (
set "reverse=!reverse!,!x%%G!"
)
)
echo %reverse%
pause
Just some quick timed testing of all 4 answers. First one uses the original string 3 characters in each of the 3 comma separated fields. The second one uses 3 characters in 9 comma separated fields. Each time I tested running each one 100 times and calculated the average. The differences are negligible.
Average of 100 tries using 3x3
Aacini 0.39254
Squashman 0.39851
Michael 0.3999
Mofi 0.40434
Average 100 tries using 3x9
Aacini 0.39925
Squashman 0.40278
Michael 0.41457
Mofi 0.43397

Capturing first 4 characters of a Windows command line output to variable

I'm trying to capture the first 4 characters of output from the following Windows command.
nltest /server:%COMPUTERNAME% /dsgetsite
What is normally returned would be:
SITEAdSiteName
This command completed successfully.
I've tried using the for /F command but can't seem to figure out how to strip everything else except the first 4 characters of what is returned.
I'm thinking using the for /F may not be the best way to accomplish this.
Are there other suggestions on how I many accomplish this?
I think my challenge is defining (or not) the delimiter to being any character, I've tried the *, but didn't seem to do it for me.
When I use this:
for /F "tokens=1-4 delims=*" %A in ('nltest /server:%COMPUTERNAME% /dsgetsite') DO echo %A
I get both output lines, sort of stumped here.
To store the first line of the output of nltest /server:%COMPUTERNAME% /DSGETSITE in variable LINE, use the following command line (use %%F instead of %F to use this in a batch file):
set "LINE=" & for /F %F in ('nltest /server:%COMPUTERNAME% /DSGETSITE') do if not defined LINE set "LINE=%F"
To return the first four characters, use sub-string expansion:
set "LINE=%LINE:~,4%"
echo %LINE%

Batch Programming. Find string after first finding the last occurence of another string

I am having some trouble writing a script that will find a string iff the string occurs on higher line number than a previously found string.
FIND /N "BEGIN" "TEST_LOG.txt" && FIND "[ERROR" TEST_LOG.txt && EXIT /B 255
This string will first return an exit code of 255 if the log has been written ("BEGIN" will always appear) and the second string "[ERROR" is found.
Because it is desirable to append to the log file I would like to ONLY look for "[ERROR" on line numbers > the max line number discovered in the FIND /N "BEGIN" statement.
For example, with a log file like this:
BEGINNING 12:03:45.17
BEGINNING 12:03:45.17
BEGINNING 12:03:45.17
BEGINNING 12:03:45.17
[ERROR
I would expect the code to exit with error code 255.
But, with a log file like this I would not:
BEGINNING 12:03:45.17
BEGINNING 12:03:45.17
[ERROR
BEGINNING 12:03:45.17
BEGINNING 12:03:45.17
ALL IS WELL!
I look forward to your replies and thank you in advance of your help.
You simply need to establish the highest line number that each strings appears, and exit if the [ERROR string line number is greater than for BEGIN. Use a FOR /F loop to process each matching line from FINDSTR /N and set a variable to the line number. The last match processed wins, and will be the highest occurrence. You need two loops - one for each search string.
I used the FINDSTR /B option to only match if the string appears at the beginning of the line. Obviously you can remove that option if it is not your requirement.
#echo off
setlocal
set begin=0
set error=0
for /f "delims=:" %%N in ('findstr /nlb BEGIN TEST_LOG.txt') do set /a begin=%%N
for /f "delims=:" %%N in ('findstr /nlb [ERROR TEST_LOG.txt') do set /a error=%%N
if %error% gtr %begin% exit /b 255

Combine multiple lines from one text file into one line

So I have a file with multiple lines of letters and numbers as shown:
a
1
h
7
G
k
3
l
END
I need a type of code that combines them together and preferably outputs it into a variable as shown:
var=a1h7Gk2l
Any help would be appreciated.
#echo off
setlocal enableDelayedExpansion
set "var="
for /f "usebackq" %%A in ("test.txt") do set var=!var!%%A
echo !var!
Edit
I assumed "END" does not physically exist in your file. If it does exist, then you can add the following line after the FOR statement to strip off the last 3 characters.
set "!var!=!var:~0,-3!"
Or, if you just want to put the result into a file (as opposed to storing it in memory for some purpose), you could do something like this:
#ECHO OFF
TYPE NUL >output.txt
FOR /F %%L IN (input.txt) DO (
IF NOT "%%L" == "END" (<NUL >>output.txt SET /P "=%%L")
)
ECHO.>>output.txt
The last command may be unnecessary, depending on whether you need the End-of-Line symbol at, well, the end of the line.

Flow control in a batch file

Reference Iterating arrays in a batch file
I have the following:
for /f "tokens=1" %%Q in ('query termserver') do (
if not ERRORLEVEL (
echo Checking %%Q
for /f "tokens=1" %%U in ('query user %UserID% /server:%%Q') do (echo %%Q)
)
)
When running query termserver from the command line, the first two lines are:
Known
-------------------------
...followed by the list of terminal servers. However, I do not want to include these as part of the query user command. Also, there are about 4 servers I do not wish to include. When I supply UserID with this code, the program is promptly exiting. I know it has something to do with the if statement. Is this not possible to nest flow control inside the for-loop?
I had tried setting a variable to exactly the names of the servers I wanted to check, but the iteration would end on the first server:
set TermServers=Server1.Server2.Server3.Server7.Server8.Server10
for /f "tokens=2 delims=.=" %%Q in ('set TermServers') do (
echo Checking %%Q
for /f "tokens=1" %%U in ('query user %UserID% /server:%%Q') do (echo %%Q)
)
I would prefer this second example over the first if nothing else for cleanliness.
Any help regarding either of these issues would be greatly appreciated.
Again, there are multiple things to note here.
if errorlevel
The help for if says:
IF [NOT] ERRORLEVEL number command
as syntax for the if errorlevel condition. That is, you must provide a number to compare against. Keep in mind that if errorlevel n evaluates to true if the exit code was at least n.
So
if errorlevel 1 ...
catches any error (that is signaled through the exit code), while
if errorlevel 0 ...
simply is always true.
Anyways, you probably want a
if not errorlevel 1 ...
here, since that condition is true if no error occurred.
Skipping lines
The for /f command has an argument skip=n which can be used to skip lines at the start. If your output starts with two lines you don't want, then you can just do
for /f "skip=2 tokens=1" %%Q in ('query termserver') do
Iterating over multiple known values in for /f
The problem with your second code snippet is that for iterates line-wise. So when you give it a single environment variable it will tokenize it (and put the tokens into different variables), but the loop runs only once per line. Also note that using set here is a bit error-prone as you might get more back than you want. Something like
for /f ... in ("%TermServers%") ...
would have been easier. Still, that doesn't solve the original problem. The easiest way to solve this would probably be something like the following:
rem space-separated list of servers
set TermServers=Server1 Server2 Server3 Server7 Server8 Server10
rem call the subroutine with the list of servers
call :query_servers %TermServers%
rem exit the batch file here, to prevent the subroutine from running again afterwards
goto :eof
rem Subroutine to iterate over the list of servers
:query_servers
rem Process the next server in the list
rem Note the usage of %1 here instead of a for loop variable
echo Checking %1
for /f "tokens=1" %%U in ('query user %UserID% /server:%1') do (echo %%Q)
rem Remove the first argument we just processed
shift
rem if there is still another server to be processed, then do so
rem we're mis-using the subroutine label as a jump target here too
if not [%1]==[] goto query_servers
rem This is kind of a "return" statement for subroutines
goto :eof
(untested, but should work.)
ETA: Gah, and once again I miss the most obvious answer:
set TermServers=Server1 Server2 Server3 Server7 Server8 Server10
for %%S in (%TermServers%) do (
for /f "tokens=1" %%U in ('query user %UserID% /server:%1') do (echo %%Q)
)
Note that this is simply for, not for /f and it will dutifully iterate over a list of values. I don't know how I missed that one, sorry.
NT shell/batch language is not smart enough to accept IF NOT ERRORLEVEL (... -- you need to do an explicit comparison, like this:
if not %ERRORLEVEL%==0 (
...

Resources