Exclamation mark removed as for variable assignment side effect - windows

I'm trying to process property file and replace some of the properties.
While I managed to implement it with
#echo OFF
set "file=my.properties"
(for /F "tokens=1* delims=]" %%A in ('type "%file%" ^| find /V /N ""') do (
echo.%%B
)) > output.txt
as a side effect all exclamation marks (!) were removed from result file. Is this a bug in windows batch or am I missing something?

You could solve the problem with the exclamation marks with a setlocal DisableDelayedExpansion.
Because with enabled delayed expansion, the expansion of %%B drops the exclamation marks.
But there are some more problems.
Using echo. is slow and fails with some content in your file like \..\windows\system32\calc.exe.
delims=] removes the line number from FIND /N but it will also remove all ] at the beginning of lines.
A better technic is to toggle the delayed expansion mode.
#echo OFF
set "file=my.properties"
setlocal DisableDelayedExpansion
(for /F "tokens=* delims=" %%A in ('findstr /n "^" "%file%"') do (
set "line=%%A"
setlocal EnableDelayedExpansion
set "line=!line:*:=!"
(echo(!line!)
endlocal
)) > output.txt

Related

How to extract the last word from the last line of a TXT file through a batch script

I am trying to extract the last word from the last line of a txt file.
The result I want is just Cup$2!.
This is what I tried:
#echo off
SetLocal EnableDelayedExpansion
set L=1
for /F "tokens=2 delims=" %%a in (corner.txt) do (
set line=%%a
if !L!==7 set Line7=%%a
set /a L=!L!+1
)
echo The word is %Line7%
pause
The result I'm getting is The word is.
What should I edit to get the above result?
Get line count.
for /f "tokens=3*" %%i in ('find /c /v /n /i"" corner.txt') do set /a v=%%i-1
Then get the last values from 7-th word of the last line:
for /f "tokens=7*" %%a in ('more corner.txt +%v%') do set "String="%%b""
Variable %String% keeps the values framed by double quotes: Cup&2!/Cup$2!
If you use / as delimiter you can get last value:
for /f "delims=/ tokens=2*" %%a in (%String%) do #echo %%a
Here's a quick example of how you could capture the substring you require, from reading your code, i.e. the last substring on the seventh line:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "var="
For /F UseBackQ^ Skip^=7^ Delims^=^ EOL^= %%G In ("corner.txt") Do Set "var=%%~nxG" & GoTo Next
If Not Defined var GoTo EndIt
:Next
SetLocal EnableDelayedExpansion
Echo The word is !var!
EndLocal
:EndIt
Echo Press any key to close . . .
Pause 1>NUL
EndLocal
Exit /B
If however, as your question title and body asks, you want the last substring of the last line, it's a little bit simpler:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "var="
For /F UseBackQ^ Delims^=^ EOL^= %%G In ("corner.txt") Do Set "var=%%~nxG"
If Not Defined var GoTo EndIt
SetLocal EnableDelayedExpansion
Echo The word is !var!
EndLocal
:EndIt
Echo Press any key to close . . .
Pause 1>NUL
EndLocal
Exit /B
I will not however be explaining any of it, please use the search facility at the top of the page, and the output from the built in help for each of the commands I have used.
Parsing strings in batch files is never ideal but this seems to work:
#echo off
setlocal ENABLEEXTENSIONS DISABLEDELAYEDEXPANSION
echo.line 1 > "%temp%\SO_Test.txt"
echo. >> "%temp%\SO_Test.txt"
echo.foo^&bar!baz hello/world Cup^&2!/Cup$2!>> "%temp%\SO_Test.txt"
goto start
:start
set "line="
for /F "usebackq delims=" %%a in ("%temp%\SO_Test.txt") do #set line=%%a
:word
set "b="
rem Delims is slash, tab, space and the order is important
for /F "tokens=1,* delims=/ " %%A in ("%line%") do (
set "line=%%A"
set "b=%%B"
)
if not "%b%" == "" (
set "line=%b%"
goto word
)
echo.Last word is "%line%"
#ECHO OFF
SETLOCAL
rem The following settings for the source directory, destination directory, target directory,
rem batch directory, filenames, output filename and temporary filename [if shown] are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q70761955.txt"
SET "amper=&"
FOR /f "usebackq delims=" %%b IN ("%filename1%") DO SET "lastword=%%b"
:: Replace ampersands with spaces, then slashes with spaces
CALL SET "lastword=%%lastword:%amper%= %%"
SET "lastword=%lastword:/= %"
FOR %%b IN (%lastword%) DO SET "lastword=%%b"
ECHO last "word" is -^>%lastword%^<-
GOTO :EOF
Read each line of the file, retaining the last-read line in lastword.
Replace each "delimiter" character (& and / are implied in the question. Others - no information.)
Select the last "word" in the last line.
You would need to change the value assigned to sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
I used a file named q70761955.txt containing your data for my testing.
You could use this in a batch-file run by cmd on windows.
FOR /F "delims=" %%A IN ('powershell -NoLogo -NoProfile -Command ^
"((Get-Content -Path 'corner.txt' -Last 1) -split '[\s*/]')[-1]"') DO (SET "LINE7=%%A")
ECHO %LINE7%
If the script were written in PowerShell instead of cmd language, it could be a one-liner.
$Line7 = ((Get-Content -Path 'corner.txt' -Last 1) -split '[\s*/]')[-1]

How can I use a batch file to find a string in a text file and replace the entire line while keeping blank lines [duplicate]

This DOS batch script is stripping out the blank lines and not showing the blank lines in the file even though I am using the TYPE.exe command to convert the file to make sure the file is ASCII so that the FIND command is compatible with the file. Can anyone tell me how to make this script include blank lines?
#ECHO off
FOR /F "USEBACKQ tokens=*" %%A IN (`TYPE.exe "build.properties" ^| FIND.exe /V ""`) DO (
ECHO --%%A--
)
pause
That is the designed behavior of FOR /F - it never returns blank lines. The work around is to use FIND or FINDSTR to prefix the line with the line number. If you can guarantee no lines start with the line number delimiter, then you simply set the appropriate delimiter and keep tokens 1* but use only the 2nd token.
::preserve blank lines using FIND, assume no line starts with ]
::long lines are truncated
for /f "tokens=1* delims=]" %%A in ('type "file.txt" ^| find /n /v ""') do echo %%B
::preserve blank lines using FINDSTR, assume no line starts with :
::long lines > 8191 bytes are lost
for /f "tokens=1* delims=:" %%A in ('type "file.txt" ^| findstr /n "^"') do echo %%B
::FINDSTR variant that preserves long lines
type "file.txt" > "file.txt.tmp"
for /f "tokens=1* delims=:" %%A in ('findstr /n "^" "file.txt.tmp"') do echo %%B
del "file.txt.tmp"
I prefer FINDSTR - it is more reliable. For example, FIND can truncate long lines - FINDSTR does not as long as it reads directly from a file. FINDSTR does drop long lines when reading from stdin via pipe or redirection.
If the file may contain lines that start with the delimiter, then you need to preserve the entire line with the line number prefix, and then use search and replace to remove the line prefix. You probably want delayed expansion off when transferring the %%A to an environment variable, otherwise any ! will be corrupted. But later within the loop you need delayed expansion to do the search and replace.
::preserve blank lines using FIND, even if a line may start with ]
::long lines are truncated
for /f "delims=" %%A in ('type "file.txt" ^| find /n /v ""') do (
set "ln=%%A"
setlocal enableDelayedExpansion
set "ln=!ln:*]=!"
echo(!ln!
endlocal
)
::preserve blank lines using FINDSTR, even if a line may start with :
::long lines >8191 bytes are truncated
for /f "delims=*" %%A in ('type "file.txt" ^| findstr /n "^"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
set "ln=!ln:*:=!"
echo(!ln!
endlocal
)
::FINDSTR variant that preserves long lines
type "file.txt" >"file.txt.tmp"
for /f "delims=*" %%A in ('findstr /n "^" "file.txt.tmp"') do (
set "ln=%%A"
setlocal enableDelayedExpansion
set "ln=!ln:*:=!"
echo(!ln!
endlocal
)
del "file.txt.tmp"
If you don't need to worry about converting the file to ASCII, then it is more efficient to drop the pipe and let FIND or FINDSTR open the file specified as an argument, or via redirection.
There is another work around that completely bypasses FOR /F during the read process. It looks odd, but it is more efficient. There are no restrictions with using delayed expansion, but unfortunately it has other limitations.
1) lines must be terminated by <CR><LF> (this will not be a problem if you do the TYPE file conversion)
2) lines must be <= 1021 bytes long (disregarding the <CR><LF>)
3) any trailing control characters are stripped from each line.
4) it must read from a file - you can't use a pipe. So in your case you will need to use a temp file to do your to ASCII conversion.
setlocal enableDelayedExpansion
type "file.txt">"file.txt.tmp"
for /f %%N in ('find /c /v "" ^<"file.txt.tmp"') do set cnt=%%N
<"file.txt.tmp" (
for /l %%N in (1 1 %cnt%) do(
set "ln="
set /p "ln="
echo(!ln!
)
)
del "file.txt.tmp"
I wrote a very simple program that may serve as replacement for FIND and FINDSTR commands when they are used for this purpose. My program is called PIPE.COM and it just insert a blank space in empty lines, so all the lines may be directly processed by FOR command with no further adjustments (as long as the inserted space don't cares). Here it is:
#ECHO off
if not exist pipe.com call :DefinePipe
FOR /F "USEBACKQ delims=" %%A IN (`pipe ^< "build.properties"`) DO (
ECHO(--%%A--
)
pause
goto :EOF
:DefinePipe
setlocal DisableDelayedExpansion
set pipe=´)€ì!Í!ŠÐŠà€Ä!€ü.t2€ü+u!:æu8²A€ê!´#€ì!Í!².€ê!´#€ì!Í!²+€ê!´#€ì!Í!Šò€Æ!´,€ì!Í!"Àu°´LÍ!ëÒ
setlocal EnableDelayedExpansion
echo !pipe!>pipe.com
exit /B
EDIT: Addendum as answer to new comment
The code at :DefinePipe subroutine create a 88 bytes program called pipe.com, that basically do a process equivalent to this pseudo-Batch code:
set "space= "
set line=
:nextChar
rem Read just ONE character
set /PC char=
if %char% neq %NewLine% (
rem Join new char to current line
set line=%line%%char%
) else (
rem End of line detected
if defined line (
rem Show current line
echo %line%
set line=
) else (
rem Empty line: change it by one space
echo %space%
)
)
goto nextChar
This way, empty lines in the input file are changed by lines with one space, so FOR /F command not longer omit they. This works "as long as the inserted space don't cares" as I said in my answer.
Note that the pipe.com program does not work in 64-bits Windows versions.
Antonio
Output lines including blank lines
Here's a method I developed for my own use.
Save the code as a batch file say, SHOWALL.BAT and pass the source file as a command line parameter.
Output can be redirected or piped.
#echo off
for /f "tokens=1,* delims=]" %%a in ('find /n /v "" ^< "%~1"') do echo.%%ba
exit /b
EXAMPLES:
showall source.txt
showall source.txt >destination.txt
showall source.txt | FIND "string"
An oddity is the inclusion of the '^<' (redirection) as opposed to just doing the following:
for /f "tokens=1,* delims=]" %%a in ('find /n /v "" "%~1"') do echo.%%ba
By omitting the redirection, a leading blank line is output.
Thanks to dbenham, this works, although it is slightly different than his suggestion:
::preserve blank lines using FIND, no limitations
for /f "USEBACKQ delims=" %%A in (`type "file.properties" ^| find /V /N ""`) do (
set "ln=%%A"
setlocal enableDelayedExpansion
set "ln=!ln:*]=!"
echo(!ln!
endlocal
)
As mentioned in this answer to the above question, it doesn't seem that lines are skipped by default using for /f in (at least) Windows XP (Community - Please update this answer by testing the below batch commands on your version & service pack of Windows).
EDIT: Per Jeb's comment below, it seems that the ping command, in at least Windows XP, is
causing for /f to produce <CR>'s instead of blank lines (If someone knows specifically why, would
appreciate it if they could update this answer or comment).
As a workaround, it seems that the second default delimited token (<space> / %%b in the example)
returns as blank, which worked for my situation of eliminating the blank lines by way of an "parent"
if conditional on the second token at the start of the for /f, like this:
for /f "tokens=1,2*" %%a in ('ping -n 1 google.com') do (
if not "x%%b"=="x" (
{do things with non-blank lines}
)
)
Using the below code:
#echo off
systeminfo | findstr /b /c:"OS Name" /c:"OS Version"
echo.&echo.
ping -n 1 google.com
echo.&echo.
for /f %%a in ('ping -n 1 google.com') do ( echo "%%a" )
echo.&echo.&echo --------------&echo.&echo.
find /?
echo.&echo.
for /f %%a in ('find /?') do ( echo "%%a" )
echo.&echo.
pause
.... the following is what I see on Windows XP, Windows 7 and Windows 2008, being the only three versions & service packs of Windows I have ready access to:

How to add incrementing number prefix order by date on multiple files in Batch Script?

I'm using window OS, and I downloaded video as series lesson. I would like order those videos by date and adding prefix number for first video as 1 than 2, 3..... I found answer at here How to rename and add incrementing number suffix on multiple files in Batch Script? it can add number to filename, but can only order by name not date. Thank and sorry for my English.
Here is a simple implementation of what aschipfl suggested in his comment:
#echo off
setlocal enableDelayedExpansion
set /a n=0
for /f "delims=" %%F in ('dir /b /od') do (
set /a n+=1
ren "%%F" "!n!_%%F"
)
But there are two potential problems:
1) The FOR /F command has a default EOL character of ; - if a file name were to start with ;, then it would be skipped. The simple solution is to set EOL to a character that cannot appear in a file name - : is a good choice.
2) The rename will fail if a filename contains a ! character because delayed expansion is enabled, and %%F is expanded before delayed expansion. The solution is to save the file name to a variable, and then toggle delayed expansion ON and OFF within the loop
#echo off
setlocal disableDelayedExpansion
set /a n=0
for /f "eol=: delims=" %%F in ('dir /b /od') do (
set "file=%%F"
set /a n+=1
setlocal enableDelayedExpansion
ren "!file!" "!n!_!file!"
endlocal
)
There is a simpler option - use FINDSTR /N to prefix the DIR /B /OD output with the line number, and then use FOR /F to parse out the number and the file name. Now there is no need for a counter variable or delayed expansion.
#echo off
for /f "delims=: tokens=1*" %%A in ('dir /b /od ^| findstr /n "^"') do ren "%%B" "%%A_%%B"
You don't even need a batch file. You could use the following directly from the command line:
for /f "delims=: tokens=1*" %A in ('dir /b /od ^| findstr /n "^"') do ren "%B" "%A_%B"

Windows Batch: How to keep empty lines with loop for /f

I'm searching how to keep empty lines when I browse a file with a for loop.
for /f "tokens=1* delims=[" %%i in ('type "test1.txt" ^| find /v /n ""') do (
SET tmp=%%i
echo !tmp! >> test2.txt
)
Actually it works for everybody, but as far as I'm concerned it does not work.
For instance if test1.txt content is:
Hello I come from France
I live in Paris
I'm sorry I don't know English, could we speak French please?
If it doesn't bother you
Thank you
Result in test2.txt will be:
[1
[2
[3
[4
[5
[6
[7
If I put off the "1" near the star "*", result is:
[1]Hello I come from France
[2]I live in Paris
[3]
[4]I'm sorry I don't know English, could we speak French please?
[5]If it doesn't bother you
[6]
[7]Thank you
Desired output is:
Hello I come from France
I live in Paris
I'm sorry I don't know English, could we speak French please?
If it doesn't bother you
Thank you
Can you please help me to solve this trouble?
This could be done as
#echo off
setlocal enableextensions disabledelayedexpansion
for /f "tokens=1,* delims=]" %%a in ('
find /n /v "" ^< "file1.txt"
') do (
>> "file2.txt" echo(%%b
)
The output from the inner find command is like
[123]texttexttext
The code uses the closing bracket as delimiter, so the tokens (we are requesting two tokens: 1,* or 1*) are
[123 texttexttext
^ ^
1 2
%%a %%b
But, as repeated delimiters are handled as only one delimiter, if one line begins with a closing bracket it will be removed. This can be prevented
as
#echo off
setlocal enableextensions disabledelayedexpansion
for /f "tokens=1,* delims=0123456789" %%a in ('
find /n /v "" ^< "file1.txt"
') do (
set "line=%%b"
setlocal enabledelayedexpansion
>>"file2.txt" echo(!line:~1!
endlocal
)
Here the numbers are used as delimiters and the line is tokenized as
[ ]texttexttext
^ ^
%%a %%b
Then the value of the second token is stored inside a variable, with delayed expansion disabled to avoid problems with exclamations inside the data (that will be handled/replaced by the parser if delayed expansion is active)
Once the data is inside the variable, delayed expansion is activated (something needed as we want to retrieve the contents from a variable changed inside a block of code) to output the line from the second position (first character in string is 0) to remove the closing bracket. Once done, delayed expansion is disabled again.
edited as the OP has to incorporate it to a larger/complex script, this code should face the most usual problems
#echo off
rem For this test we will have delayed expansion from the start
setlocal enableextensions enabledelayedexpansion
rem External code block that will make delayed expansion necessary
if 1==1 (
rem Variables changed inside block
set "input_file=file1.txt"
set "output_file=file2.txt"
rem Grab a reference to the content of the file variables
for %%i in ("!input_file!") do for %%o in ("!output_file!") do (
rem Prepare the environment for file work
setlocal disabledelayedexpansion
rem Prepare output file
type nul > "%%~fo"
rem Process input file and write to output file
for /f "tokens=1,* delims=0123456789" %%a in ('
find /n /v "" ^< "%%~fi"
') do (
set "line=%%b"
setlocal enabledelayedexpansion
>>"%%~fo" echo(!line:~1!
endlocal
)
rem Restore the previous environment
endlocal
)
)
Here is a slightly different variant using the findstr command rather than find and doing the redirection to the output file file2.txt only once rather than per for /F loop iteration:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
>> "file2.txt" (
for /F "delims=" %%a in ('findstr /N "^" "file1.txt"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
echo(!line:*:=!
endlocal
)
)
endlocal
The findstr command precedes every line by a line number and a colon, like this:
1:Hello I come from France
The sub-string substitution portion !line:*:=! replaces everything up to the first colon (due to *) by nothing, thus removing this.
Replace the >> operator by > in case you want to overwrite an already existing file rather than to append to it.
Send your loop command to con: device:
In command line:
for /F tokens^=^* %F in ('type File.txt ^>con:')do #echo/%F
In your bat file:
for /F tokens^=^* %%F in ('type File.txt ^>con:')do echo;%%F

Read stdin stream in a batch file

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!
)

Resources