I have this single line CMD file TEST.CMD:
for %%f in (%1 %2 %3 %4 %5 %6 %7 %8) DO ECHO %%f
If I run this:
TEST this is a test
it correctly echos each parameter on a separate line, i.e.,
this
is
a
test
However if a parameter contains asterisk it skips it. E.g.,
TEST this is a* test
Results in:
this
is
test
How do I get the parameter with an asterisk to be treated like a normal token?
Thanks.
The simplest method that works for most parameters is to transfer the parameters to an "array" of variables, and then use FOR /L to loop through the array. This is best achieved with delayed expansion.
This technique can process an arbitrary number of parameters - it is not limited to 9.
#echo off
setlocal
:: Transfer parameters to an "array"
set arg.cnt=1
:getArgs
(set arg.%arg.cnt%=%1)
if defined arg.%arg.cnt% (
set /a arg.cnt+=1
shift /1
goto :getArgs
)
set /a arg.cnt-=1
:: Process the "array"
setlocal enableDelayedExpansion
for /l %%N in (1 1 %arg.cnt%) do echo arg %%N = !arg.%%N!
The only way I have found without knowing the parameters beforehand is to echo the parameters in the for loop
for /f %%f in ('"echo %1 && echo %2 && echo %3 && etc"') DO ECHO %%f
You can't print that, the asterisk is a dynamic operator that matches "1 or more characters" in some commands, like the FOR command, the only way is to use the /F parameter that gets the output of a command.
See what happens if you use this:
#Echo OFF
Pushd "C:\"
Call :sub a b c * d e
:sub
for %%f in (%1 %2 %3 %4 %5 %6 %7 %8) DO ECHO %%f
Pause&Exit
(The FOR prints all the files in current directory)
Then you need to do... :
#Echo OFF
Call :sub a b c* d e
:sub
FOR /F "tokens=*" %%a in ('Echo %*') DO (ECHO %%a)
Pause&Exit
Related
I have a phrase that needs to be outputted in results.txt. The phrase comes from x.txt. For example: "I have two kids", it should output "kids two have I" in results.txt.
UPDATE
Its already working but i want no loop.
Pls see code below
Code
#ECHO OFF
set /p content=<x.txt
SET var=!content: =,!
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 !rev:,= ! > results.txt
This takes the first four words (of each line) of the text file and rewrites them in reverse order to result.txt:
>result.txt (for /f "tokens=1-4" %%a in (x.txt) do echo %%d %%c %%b %%a)
Another solution (for a one-line text file, unspecified number of words):
#ECHO OFF
setlocal enabledelayedexpansion
echo I have two little but wonderful kids>x.txt
<x.txt set /p x=
for %%a in (%x%) do set "res=%%a !res!"
>result.txt echo %res:~0,-1%
(although technically, the for command is a loop on its own)
Without any form of a loop, if you can live with some spaces at the front:
#ECHO OFF
setlocal
echo I have two kids>x.txt
<x.txt set /p x=
call :reverse %x%
goto :eof
:reverse
set "rev=%9 %8 %7 %6 %5 %4 %3 %2 %1"
echo Reverse without any form of loop: "%rev%"
for /f "tokens=*" %%a in ("%rev%") do echo To get rid of the spaces, you need a FOR loop: "%%a"
This is limited to a maximum of nine words because cmd supports only %1to %9
You can use more parameters (words) by using the shift command, but that would mean using a loop.
Here are three different ways.
Replacement magic (borrowed from Aacini)
For tokens
Recursion
#echo off
setlocal EnableDelayedExpansion
set "var=One two three four five"
call :rev1
call :rev2
call :rev3
exit /b
:rev1 -- replacement magic
setlocal
set "rev="
set rev=%var: = !rev!&Set rev=% !rev!
echo %0 !rev!
exit /b
:rev2 -- FOR toekns
setlocal
FOR /F "tokens=1-5" %%1 in ("%var%") do set "rev=%%5 %%4 %%3 %%2 %%1"
echo %0 !rev!
exit /b
:rev3 -- recursion
setlocal
set "rev="
call :__rev3_rec %var%
echo %0 !rev!
exit /b
:__rev3_rec
if "%1" == "" exit /b
call :__rev3_rec %2 %3 %4 %5
set "rev=%rev%%1 "
exit /b
I'm not sure if I've understood your question fully, so this is intended to reverse the order of each space separated substring per line.
For the task, I'd leverage PowerShell.
At the powershell prompt, (powershell.exe):
GC '.\x.txt'|%{$L=$_.Split(' ');[Array]::Reverse($L);$L -Join ' '}|SC '.\results.txt'
At the command-prompt, (cmd.exe):
"%__AppDir__%WindowsPowerShell\v1.0\powershell.exe" -NoP "GC '.\x.txt'|%{$L=$_.Split(' ');[Array]::Reverse($L);$L -Join ' '}|SC '.\results.txt'
From a batch-file:
#"%__AppDir__%WindowsPowerShell\v1.0\powershell.exe" -NoP "GC '.\x.txt'|%%{$L=$_.Split(' ');[Array]::Reverse($L);$L -Join ' '}|SC '.\results.txt'
If you're only working on the first line of the file, as opposed to all of them, a small modification is needed.
At the powershell prompt, (powershell.exe):
GC '.\x.txt' -To 1|%{$L=$_.Split(' ');[Array]::Reverse($L);$L -Join ' '}|SC '.\results.txt'
At the command-prompt, (cmd.exe):
"%__AppDir__%WindowsPowerShell\v1.0\powershell.exe" -NoP "GC '.\x.txt' -To 1|%{$L=$_.Split(' ');[Array]::Reverse($L);$L -Join ' '}|SC '.\results.txt'
From a batch-file:
#"%__AppDir__%WindowsPowerShell\v1.0\powershell.exe" -NoP "GC '.\x.txt' -To 1|%%{$L=$_.Split(' ');[Array]::Reverse($L);$L -Join ' '}|SC '.\results.txt'
Please note that, in both cases, if your strings are actually doublequoted, those will not move position. This means that "I have two kids", would output as kids" two have "I, not "kids two have I".
This is my code:
set var1="`netsh int ipv4 show interface ^| findstr /I Network`"
call:GetEles %var1%
goto:eof
:GetEles
for /F "tokens=1 usebackq" %%F IN (%~1) do echo %%F
goto:eof
When I check the command while it is running, the ^ becomes doubled inside function :GetEles:
for /F "token=1 usebackq" %%F IN (`netsh int ipv4 show interface ^^| findstr /I Network`) do echo %%F
That doubled ^ makes my script failing, how can I solve it?
As others already described, this is a nasty "feature" of the call command.
There are several options to work around that:
Simply undo the caret doubling in the sub-routine:
#echo off
set "VAR=caret^symbol"
call :SUB "%VAR%"
exit /B
:SUB
set "ARG=%~1"
echo Argument: "%ARG:^^=^%"
exit /B
call introduces a second parsing phase, so let the second one expand the variable:
#echo off
set "VAR=caret^symbol"
call :SUB "%%VAR%%"
exit /B
:SUB
echo Argument: "%~1"
exit /B
Pass the value by reference (so the variable name) rather than by value:
#echo off
set "VAR=caret^symbol"
call :SUB VAR
exit /B
:SUB
setlocal EnableDelayedExpansion
echo Argument: "!%~1!"
endlocal
exit /B
Do not pass the variable value to the sub-routine, read the (global) variable there instead:
#echo off
set "VAR=caret^symbol"
call :SUB
exit /B
:SUB
echo Argument: "%VAR%"
exit /B
Read Buggy behaviour when using CALL:
Redirection with & | <> does not work as expected.
If the CALL command contains a caret character within a quoted string
"test^ing", the carets will be doubled.
Try following code snippet:
#echo off
SETLOCAL EnableExtensions
set "var1=`netsh int ipv4 show interface ^| findstr /I "Network"`"
call:GetEles "%var1%"
goto:eof
:GetEles
echo variable "%var1%"
echo parameter "%~1"
for /F "tokens=1 usebackq" %%F IN (%var1%) do echo %%F
goto:eof
Output:
d:\bat> D:\bat\SO\41769803.bat
variable "`netsh int ipv4 show interface ^| findstr /I "Network"`"
parameter "`netsh int ipv4 show interface ^^| findstr /I "Network"`"
Try it like this, (the call will not expand the %var1% to a point which will expose the poison character).
set "var1='netsh int ipv4 show interface ^| findstr /I Network'"
call:GetEles "%%var1%%"
goto:eof
:GetEles
for /F %%F IN (%~1) do echo %%F
goto:eof
You will note that tokens=1 wasn't needed and neither was usebackq
apparently this batch file should return the concatenation of the input files given as arguments, but it is not working:
set files=
for %%i in (%1 %2 %3 %4 %5 %6 %7 %8) do (
echo %%i
set files=%files% %%i
)
echo "the file list is %files%"
when invoked with:
mybatchfile.bat example1.txt example2.txt
the expected result should be:
example1.txt
example2.txt
the file list is example1.txt example2.txt
but in the final line only has "example2.txt". Any idea???
You need to enable delayed variable expansion (note the expansion !files! in the following code):
set files=
setlocal EnableDelayedExpansion
for %%i in (%1 %2 %3 %4 %5 %6 %7 %8) do (
echo %%i
set files=!files! %%i
)
endlocal & set files=%files%
echo "the file list is %files%"
I've got bunch of text files with some content. First I wanted to number the lines globally. Then I extracted all lines that are duplicated somewhere (occur in any of given files at least twice). But now I need to mark all of these lines with the filename and line number of the first occurrence of this line. And now the funny part - it needs to be a windows batch file, using native windows tools. That's why I've got this problem to begin with.
So, to sum it up:
I have a file A with unique strings/lines, each of them is said to occur at least twice in given set of files.
I need to search these files and mark all occurrences of given line from A file with
-file name in which the line first occured
-line number in this file
This is my code with effort to number lines and format files.
#echo off
setlocal EnableDelayedExpansion
set /a lnum=0
if not [%1]==[] pushd %1
for /r %%F in (*.txt) do call :sub "%%F"
echo Total lines in %Files% files: %Total%
popd
exit /b 0
:Sub
set /a Cnt=0
for /f %%n in ('type %1') do (
set /a Cnt+=1
set /a lnum=!lnum!+1
echo ^<!lnum!^> %%n >> %1_ln.txt && echo ^<!lnum!^> >> %1_ln.txt && echo. >> %1_ln.txt
)
set /a Total+=Cnt
set /a Files+=1
echo %1: %Cnt% lines
#echo off
setlocal EnableDelayedExpansion
set lnum=0
if not "%~1" == "" pushd %1
rem "I've got bunch of text files..." (%%F is file name)
for /r %%F in (*.txt) do call :sub "%%F"
echo Total lines in %Files% files: %lnum%
popd
exit /b 0
:Sub "filename"
set Cnt=0
rem "... with some content." (%%n is line contents)
(for /f "usebackq delims=" %%n in (%1) do (
set /a Cnt+=1
rem "First I wanted to number the lines globally."
set /a lnum+=1
echo ^<!lnum!^> %%n
rem "Then I extracted all lines that are duplicated somewhere" (that were defined before)
if defined line[%%n] (
rem "I need to mark all of these lines with the filename and line number of the first occurrence of this line."
echo ^<!line[%%n]!^>
echo/
) else (
REM (Store the first occurrence of this line with *local* line number and filename)
set line[%%n]=!Cnt!: %1
)
)) > "%~PN1_ln.txt"
set /A Files+=1
echo %1: %Cnt% lines
exit /B
The above Batch program ignore empty lines in the input files and fail if they contain special Batch characters, like ! & < > |; this limitation may be fixed if required.
#ECHO OFF
SETLOCAL
FOR /f "delims=" %%s IN (A) DO (
SET searching=Y
FOR /f "delims=" %%f IN (
'dir /s /b /a-d *.txt') DO IF DEFINED searching (
FOR /f "tokens=1delims=:" %%L IN (
'findstr /b /e /n /l /c:"%%s" ^<"%%f"') DO IF DEFINED searching (
ECHO Line %%L IN "%%f" FOUND "%%s"
SET "searching="
)
)
)
Here's the meat of a routine that should do what you appear to be looking for - and that's as clear as mud.
It looks through the "A" file for each string in turn, assigns the string to %%s and sets the flag searching
Then it looks through the file list, assigning filenames to %%f
Then it executes a findstr to find the /c:"%%s" complete string %%s (including any spaces) in /l or literal mode (ie. not using regular expressions) for a line that both /b and /e begins and ends with the target (ie exactly matches) and /n numbers those lines.
The output of findstr will be in the format linenumber:linecontents so if this line is examined by the FORwith the option "delims=:" then the partion up to the first : is assigned to to %%L
So - %%L contains the line#, %%f the filename, %%s the string
Clearing searching having detected this line by setting its value to [nothing] means it's not NOT DEFINED hence no further lines will be reported from the current file, and no further filenames will be examined.
Now if you want to get a listing of ALL of the occurrences of the target lines, all you need to do is to REM-out the SET "searching=" line. Searching will then never be reset, so each line in each file is reported.
If you want some other combination, please clarify.
I have absolutely no idea whatever what you mean by "marking" a line.
#ECHO OFF & setlocal
for /f "tokens=1*delims==" %%i in ('set "$" 2^>nul') do set "%%i="
for %%a in (*.txt) do (
for /f %%b in ('find /v /c "" ^<"%%a"') do echo(%%b lines in %%a.
set /a counter=0, files+=1
for /f "usebackqdelims=" %%b in ("%%~a") do (
set /a counter+=1, total+=1
set "line=%%b"
setlocal enabledelayedexpansion
if not defined $!line! set "$!line!=%%a=!counter!=!line!"
for /f "delims=" %%i in ('set "$" 2^>nul') do (if "!"=="" endlocal)& set "%%i"
)
)
echo(%total% lines in %files% files.
for /f "delims=" %%a in (a) do set "#%%a=%%a"
for /f "tokens=2,3*delims==:" %%i in ('set "$" 2^>nul') do (
if defined #%%k echo("%%k" found in %%i at line %%j.
)
Script can handle !&<>|%, but not =.
In windows batch files, I know %1 is replaced with the first argument, %2 is replaced with the 2nd argument and %* is replaced with all arguments.
Is there a way to get all the arguments after the 1st one? (e.g. arguments 2-N) What about all the arguments after the 2nd one?
The SHIFT command doesn't seem to affect %*.
#ECHO OFF
SETLOCAL
CALL :allafter 3 %*
ECHO args=%args%
GOTO :eof
:allafter
FOR /l %%a IN (1,1,%1) DO SHIFT
(SET args=)
:argloop
shift
IF NOT .%1==. SET args=%args% %1&GOTO argloop
IF DEFINED args SET args=%args:~1%
GOTO :eof
to get everything after the 3rd argument to ARGS
Edit - to take care of space-separated elements which may include commas
#ECHO OFF
SETLOCAL
CALL :allafter 3 %*
ECHO args=%args%
CALL :allafter2 3 %*
ECHO args=%args%
GOTO :eof
:allafter
FOR /l %%a IN (1,1,%1) DO SHIFT
(SET args=)
:argloop
shift
IF NOT .%1==. SET args=%args% %~1&GOTO argloop
IF DEFINED args SET args=%args:~1%
GOTO :EOF
:allafter2
SET /a count=%1
SET args=%*
:arg2loop
SET oldargs=%args%
call SET args=%%args:*%1 =%%
IF "%args%"=="%oldargs%" (call SET args=%%args:*%1,=%%) ELSE (SET /a count-=1)
shift
IF %count% gtr -1 GOTO arg2loop
GOTO :EOF
Hmm- spoke too soon. This modified routine should play nicer. The previous version treated what was to be defined as one argument one,two,three as three separate arguments when the remove-leading-n was invoked.
Well, there is a way-ish...
By using the /n switch on the shift command, you can sort of do something like it. However, it will delete all of the argument and put them into a certain variable (so you can't call %3 anymore without a for loop).
#setlocal enableextensions
#echo off
:loop
if "%~2" equ "" goto end
set variable=%variable% %~2
shift /2
goto loop
:end
echo %1
echo %variable%
endlocal
To separate the parameters again just do a simple for loop (I'm sure you can find documentation on it somewhere).
Solution without shift & goto:
#echo off &setlocal enabledelayedexpansion
set /a count=0
for %%i in (%*) do set /a count+=1
set "args="
for /l %%i in (2,1,%count%) do if defined args (call set "args=!args! %%%%i") else call set "args=%%%%i"
echo.%args%
endlocal
> type t.bat
#echo off
echo %*
for /f "tokens=1,*delims= " %%i in ("%*") do echo %%j
> t a b c d e f,g h i
a b c d e f,g h i
b c d e f,g h i
> t a,a b c d e f,g h i
a,a b c d e f,g h i
b c d e f,g h i
>