I have this little snippet that replaces production:false with production:true.
(FOR /F "tokens=1,* delims=]" %%A in ('"type test.js|find /n /v """') do (
set "line=%%B"
if defined line (
call set "line=echo.%%line:production:false=production:true%%"
FOR /F "delims=" %%X in ('"echo."%%line%%""') do %%~X
) ELSE echo.
)) >test-temp.js
move /Y test-temp.js test.js
So far so good, but, in the test.js it says something like:
if ( !production ) {}
The thing is, the "!" is removed by the above command as well. Any idea how does happens?
You probably have delayedexpansion invoked.
Running your snippet (which is all you give us) against your single line of provided data simply reproduced the data line verbatim.
with delayedexpansion, the ! disappeared as you describe.
To fix (with delayedexpansion in effect
SETLOCAL DISABLEDELAYEDEXPANSION
(FOR /F "tokens=1,* delims=]" %%A in ('"type q19406461.txt|find /n /v """') do (
set "line=%%B"
if defined line (
call set "line=echo.%%line:production:false=production:true%%"
FOR /F "delims=" %%X in ('"echo."%%line%%""') do %%~X
) ELSE echo.
)) >test-temp.js
ENDLOCAL
This is a robust method and uses a helper batch file called repl.bat from - http://www.dostips.com/forum/viewtopic.php?f=3&t=3855
Put repl.bat in the same folder as the batch file.
#echo off
type "test.js"|repl "production:false" "production:true" >"test-temp.js"
Peter already diagnosed the problem with delayed expansion. Here is a bit more explanation.
FOR variables are expanded before delayed expansion. So the line set "line=%%B" first sets the value to if ( !production ) {}, but then delayed expansion sees the unpaired ! and strips it. If it were paired, it would attempt to expand the variable in between.
Here is a summary of the order of variable expansion (practical, but a bit imprecise):
1) Normal expansion: argument (%1) and variable (%var%). Arguments take precedence over variables.
2) FOR variable: %%A
3) Delayed expansion: !var!
4) SET /A variables
See How does the Windows Command Interpreter (CMD.EXE) parse scripts for a more exact description of how lines are parsed and expanded.
It is possible to use delayed expansion within your loop without corrupting ! literals if you toggle delayed expansion on and off. This is preferred over using the CALL syntax because it is faster, does not muck with % literals, does not double quoted carets, protects against all poison characters, even when they are not quoted.
The use of delayed expansion makes the code much simpler, faster, and more reliable:
setlocal disableDelayedExpansion
(FOR /F "tokens=1,* delims=]" %%A in ('"type test.js|find /n /v """') do (
set "line=%%B"
setlocal enableDelayedExpansion
if defined line (
echo(!line:production:false=production:true!
) ELSE echo(
endlocal
)) >test-temp.js
move /Y test-temp.js test.js
endlocal
Note that I use ECHO( instead of ECHO. because the latter can fail in some obscure scenarios. ECHO( looks like it would cause problems with block parentheses, but it actually always works without any problems.
Note that I would still not use the above code. Instead I would use the REPL.BAT utility as foxidrive has in his answer.
Related
I may be missing something or forgotten something but when I attempt to "SET" a variable within a batch file the value of another variable from a FOR loop, it does not work.
ECHO OFF
set /p inputfile="Enter the server list name: "
for /f %%z in (%inputfile%) do (
for /f "skip=1 tokens=1,2,3 delims=," %%a in (%%z.csv) do (
set inst=%%b
#echo Service Name: %%a
#echo %inst%
#echo Description: %%b
#echo Install Path: %%c
)
)
pause
My end goal is to replace the "\" present within the install path retrieved with "/" so that it will be compatible with a separate java app. During testing the variable "inst" comes back empty.
The syntax I am hoping to use is here: http://ss64.com/nt/syntax-replace.html
Any suggestions?
SET command works. What you need is Enable Delayed Expansion
Delayed Expansion will cause variables to be expanded at execution
time rather than at parse time, this option is turned on with the
SETLOCAL command. When delayed expansion is in effect variables may be
referenced using !variable_name! (in addition to the normal
%variable_name%)
ECHO OFF
SETLOCAL enableextensions enabledelayedexpansion
set /p "inputfile=Enter the server list name: "
for /f %%z in (%inputfile%) do (
for /f "skip=1 tokens=1,2* delims=," %%a in (%%z.csv) do (
set "inst=%%b"
#echo Service Name: %%a
#echo !inst! "!inst:\=/!"
#echo Description: %%b
#echo Install Path: %%c
)
)
pause
Brackets make multiple lines 1 line. Therefore you just need to move the echo's outside of the brackets so they are on a new line.
Variables are expanded when line is read. Therefore changes won't be noticed till the next line.
For multi command lines you can force expansion at execution time using !Var! syntax and turning this on with setlocal enabledelayedexpansion.
This is syntax to use to replace stuff set file=%windir:\=\\% replaces \ with \\ in %windir%
I'm trying to insert a line into a file using the following code (from Write batch variable into specific line in a text file)
#echo off
setlocal enableextensions enabledelayedexpansion
set inputfile=variables.txt
set tempfile=%random%-%random%.tmp
copy /y nul %tempfile%
set line=0
for /f "delims=" %%l in (%inputfile%) do (
set /a line+=1
if !line!==4 (
echo WORDS YOU REPLACE IT WITH>>%tempfile%
) else (
echo %%l>>%tempfile%
)
)
del %inputfile%
ren %tempfile% %inputfile%
endlocal
My problem is the file has comment lines (which start with semicolons) which need to be kept
; directory during network startup. This statement must indicate a local disc
; drive on your PC and not a network disc drive.
LOCALDRIVE=C:\TEMP;
; PANELISATION PART/NET NAMING CONVENTION
; When jobs are panelised, parts/nets are renamed for each panel step by
When I run the batch file, it ignores the semicolon lines, So I only get:
LOCALDRIVE=C:\TEMP;
What do I need to do to keep the semicolon lines?
The EOL option determines what lines are to be ignored. The default value is a semicolon. If you know a character that can never appear in the first position of a line, then you can simply set EOL to that character. For example, if you know a line can't start with |, then you could use
for /f "eol=| delims=" %%l in (%inputfile%) do ...
There is an awkward syntax that disables EOL completely, and also disables DELIMS:
for /f delims^=^ eol^= %%l in (%inputfil%) do ...
Note that FOR /F always discards empty lines, so either of the above would result in:
; directory during network startup. This statement must indicate a local disc
; drive on your PC and not a network disc drive.
LOCALDRIVE=C:\TEMP;
; PANELISATION PART/NET NAMING CONVENTION
; When jobs are panelised, parts/nets are renamed for each panel step by
A trick is used if you want to preserve empty lines. Use FIND or FINDSTR to insert the line number before each line, and then use expansion find/replace to remove the line number. Now you know the line never begins with ;, so you can ignore the EOL option.
for /f "delims=" %%L in ('findstr /n "^" "%inputfile%"') do (
set "ln=%%L"
set "ln=!ln:*:=!"
REM You now have the original line, do whatever needs to be done here
)
But all of the above have a potential problem in that you have delayed expansion enabled when you expand the FOR variable, which means that any content containing ! will be corrupted. To solve this you must toggle delayed expansion on and off within the loop:
setlocal disableDelayedExpansion
...
for /f "delims=" %%L in (findstr /n "^" "%inputfile%") do (
set "ln=%%L"
setlocal enableDelayedExpansion
set "ln=!ln:*:=!"
REM You now have the original line with ! preserved, do whatever needs done here
endlocal
)
Also, when ECHOing an empty line, it will print out ECHO is off unless you do something like
echo(!ln!
It takes time to open and position the write cursor to the end every time you use >> within the loop. It is faster to enclose the entire operation in one set of parentheses and redirect once. Also, you can replace the DEL and REN with a single MOVE command.
Here is a final robust script:
#echo off
setlocal disableDelayedExpansion
set "inputfile=variables.txt"
set line=0
>"%inputfile%.new" (
for /f "delims=" %%L in (findstr /n "^" "%inputfile%") do (
set "txt=%%L"
set /a line+=1
setlocal enableDelayedExpansion
set "txt=!txt:*:=!"
if !line! equ 4 (
echo New line content here
) else (
echo(!txt!
)
endlocal
)
)
move /y "%inputfile%.new" "%inputfile%" >nul
endlocal
That is an awful lot of work for such a simple task, and it requires a lot of arcane knowledge.
There is a much quicker hack that works as long as
your first 4 lines do not exceed 1021 bytes
none of your first 3 lines have trailing control characters that need to be preserved
the remaining lines do not have <tab> characters that must be preserved (MORE converts <tab> into a string of spaces.
#echo off
setlocal enableDelayedExpansion
set "inputfile=variables.txt"
>"%inputfile%.new" (
<"%inputfile%" (
for /l %%N in (1 1 3) do (
set "ln="
set /p "ln="
echo(!ln!
)
)
echo New line content here
more +4 "%inputfile%"
)
move /y "%inputfile%.new" "%inputfile%"
That is still a lot of work and arcane knowledge.
I would use my JREPL.BAT utility
Batch is really a terrible tool for text processing. That is why I developed JREPL.BAT to manipulate text using regular expressions. It is a hybrid JScript/batch script that runs natively on any Windows machine from XP onward. It is extremely versatile, robust, and fast.
A minimal amount of code is required to solve your problem with JREPL. Your problem doesn't really require the regular expression capabilities.
jrepl "^" "" /jendln "if (ln==4) $txt='New content here'" /f "variables.txt" /o -
If used within a batch script, then you must use call jrepl ... because JREPL.BAT is also a batch script.
By default, the FOR command treats ; as the end-of-line character, so all those lines that start with ; are being ignored.
Add eol= to your FOR command, like this:
for /f "eol= delims=" %%l in (%inputfile%) do (
It looks like you're echoing just the line delimiter, not the whole line:
echo %%l>>%tempfile%
I'm rusty on ms-dos scripts, so I can't give you more than that.
I have a batch file to Find and replace text string in a file with a portion of the its own file-name in multiple files within a folder using windows Batch script but it does not work and simply replace YYY with null or nothing. Any help appreciated. thank you
#echo off
SETLOCAL
SET stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
fOR /F "delims=" %%l IN (%%f) DO (
SET "line=%%l"
SET fname=%%~nf
SET fname=!fname:~6,3!
SETLOCAL ENABLEDELAYEDEXPANSION
set "x=!line:%stringtofindreplace%=%fname%!"
echo(!x!
ENDLOCAL)
)>%%~nf.txt
)
GOTO:EOF
here is updated code that still does not work
#echo off
SETLOCAL
SET stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
(
fOR /F "delims=" %%l IN (%%f) DO (
SET "line=%%l"
SET fname=%%~nf
SETLOCAL ENABLEDELAYEDEXPANSION
SET fname=!fname:~6,3!
SET "x=!line:%stringtofindreplace%=%fname%!"
echo(!x!
ENDLOCAL
)
)>%%~nf.txt
)
GOTO:EOF
You have a number of problems.
1) Your name substring computation attempts to use delayed expansion within your loop before you enable it.
2) The computed replacement string cannot be expanded using normal expansion. You need delayed expansion there as well. But you cannot use delayed expansion within delayed expansion. The trick is to transfer the inner value to a FOR variable.
3) You have got an extra, unbalanced ) after ENDLOCAL. I don't think it is causing any problems with the code you have posted, but you probably should remove it.
You are also computing the name substring once for every line, when it really only need be done once per file. This isn't a bug, but it is inefficient.
Here is corrected code.
#echo off
setlocal
set stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
setlocal enableDelayedExpansion
set "fname=%%~nf"
for /f "eol=: delims=" %%A in ("!fname:~6,3!") do (
endlocal
for /f "delims=" %%l IN (%%f) do (
set "line=%%l"
setlocal enableDelayedExpansion
set "x=!line:%stringtofindreplace%=%%A!"
echo(!x!
endlocal
)
)>"%%~nf.txt"
)
You still could have problems if any lines begin with ;. Also, empty lines will be stripped. Both limitations could be solved with a bit more code.
But even if you fix the limitations, it will be quite slow. It could be painfully slow if any files are large.
The code is much simpler, faster, and more reliable if you use my hybrid JScript/batch REPL.BAT utility:
#echo off
setlocal
set stringtofindreplace=YYY
for %%f in (*.fmw) do (
#echo Processing %%f...
setlocal enableDelayedExpansion
set "fname=%%~nf"
type "!fname!.fmw"|repl "%stringtofindreplace%" "!fname:~6,3!" li >"!fname!.txt"
endlocal
)
I would like to iterate a flat text file containing a Windows directory listing and run multiple commands against each line assigning each line to a variable. I then want to echo each resulting output from each command into a comma-delimited file. Yes, I understand batch programming might not be the best choice but am somewhat limited at the moment. Here is where I am at with it:
#echo off
setlocal
SETLOCAL ENABLEDELAYEDEXPANSION
:: There could be spaces in the filename, thus using delims
FOR /f "delims=?" %%a in (e:\multiline_textfile.txt) do (
set filename=%%a
CALL :PROG1
CALL :PROG2
CALL :PROG3
CALL :ENDPROG
:PROG1
FOR /f "delims=?" %%h in ('e:\apps\exe1.exe -s "!filename!"') do set result11=%%h
:PROG2
FOR /f "delims=?" %%i in ('e:\apps\exe1.exe -b"!filename!"') do set result2=%%i
:PROG3
FOR /f "delims=?" %%j in ('e:\apps\exe2.exe -c "!filename!"') do set result3=%%j
:ENDPROG
echo !result1!,!result2!,!result3!
)
---
Any insight would be greatly appreciated.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: There could be spaces in the filename, thus using delims
FOR /f "delims=" %%a in (e:\multiline_textfile.txt) do (
set "filename=%%a"
CALL :PROG1
CALL :PROG2
CALL :PROG3
CALL :ENDPROG
)
echo here endeth your batch mainline
goto :eof
:PROG1
FOR /f "delims=" %%h in ('e:\apps\exe1.exe -s "!filename!"') do set "result1=%%h"
goto :eof
:PROG2
FOR /f "delims=" %%i in ('e:\apps\exe1.exe -b"!filename!"') do set "result2=%%i"
goto :eof
:PROG3
FOR /f "delims=" %%j in ('e:\apps\exe2.exe -c "!filename!"') do set "result3=%%j"
goto :eof
:ENDPROG
echo !result1!,!result2!,!result3!
goto :eof
You don't need the initial SETLOCAL - one is quite sufficient.
Labels are simply MARKERS in a batch program - they don't affect flow. The most common way to return in a CALLed routine is to GOTO :EOF where the colon is REQUIRED and the label :EOF is implicit and should NOT be declared.
Bad idea to attempt to use labels within a FOR loop like that. Might work with later versions, but not with earlier. Note the FOR loop's ending ) has been moved...
Whereas !var! works, it is not necessary in this case because each subroutine is essentially a whole new program which inherits the main environment as it stood at the CALL. Hence each !var! could be replaced by %var% and the SETLOCAL ENABLEDELAYEDEXPANSION need only be a SETLOCAL. You only need to use delayedexpansion where an ordinary environment variable's value is to be used after being changed in a compound statement (like a FOR over multiple lines, for instance)
Is it possible to use a piped stdin stream inside a batch file?
I want to be able to redirect the output of one command into my batch file process.bat list so:
C:\>someOtherProgram.exe | process.bat
My first attempt looked like:
echo OFF
setlocal
:again
set /p inputLine=""
echo.%inputLine%
if not (%inputLine%)==() goto again
endlocal
:End
When I test it with type testFile.txt | process.bat it prints out the first line repeatedly.
Is there another way?
set /p doesn't work with pipes, it takes one (randomly) line from the input.
But you can use more inside of an for-loop.
#echo off
setlocal
for /F "tokens=*" %%a in ('more') do (
echo #%%a
)
But this fails with lines beginning with a semicolon (as the FOR-LOOP-standard of eol is ;).
And it can't read empty lines.
But with findstr you can solve this too, it prefix each line with the linenumber, so you never get empty lines.
And then the prefix is removed to the first colon.
#echo off
setlocal DisableDelayedExpansion
for /F "tokens=*" %%a in ('findstr /n "^"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
set "line=!line:*:=!"
echo(!line!
endlocal
)
Alternatively, on some environments (like WinRE) that don't include findstr, an alternative with find.exe might suffice. find will accept a null search string "", and allows search inversion. This would allow something like this:
#echo off
setlocal DisableDelayedExpansion
for /F "tokens=*" %%a in ('find /v ""') do (
...
The set "line=!line:*:=!" syntax is:
set requires one parameter that is a=b.
If a contains a space or something, you'll have to use the quotation marks around this parameter. Here I don't see any
!line:*:=!
For this syntax, you can type 'set /?' to see the official description on using variables.
!var! is like %var%, to get the value. But !var! means delayed expansion.
line var name
the first : variable modification mark.
**:= **:=(empty), replace the string in the variable's value matches "*:"(virtually from the string start to first : occurence) with (empty), i.e. delete the substring from start to first colon.
FOR /F "tokens=1* delims=]" %%A IN ('FIND /N /V ""') DO (
> CON ECHO.%%B
>> %File% ECHO.%%B
)
Source here: http://www.robvanderwoude.com/unixports.php#TEE
Alternatively, on some environments (like WinRE) that don't include findstr, an alternative with find.exe might suffice. find will accept a null search string "", and allows search inversion. This would allow something like this:
#echo off
setlocal DisableDelayedExpansion
for /F "tokens=*" %%a in ('find /v ""') do (
set "line=%%a"
echo(!line!
)