batch script - to remove duplicate tokens in file - text

I have duplicate tokens in text file I would like to create new text file without the duplicate tokens (keeping the delimiters)
The delimiter is:~#^*^#~
example file:
aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~xxx~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb~#^*^#~aaa~#^*^#~bbb
Result should be:
aaa~#^*^#~bbb~#^*^#~xxx
I found script that remove duplicate lines:
==================================
#echo off > outfile
if %1'==' echo which file? && goto :eof
if not exist %1 echo %1 not found && goto :eof
for /f "tokens=* delims= " %%a in (%1) do (
find "%%a" < outfile > nul
if errorlevel 1 echo %%a >> outfile
)
The script work nice for duplicate lines,
So i modified the delims from:
"tokens=* delims="
to
"tokens=* delims=~#^*^#~"
But it wont work, What am i doing wrong? is one of the delimiter characters reserved word?
Thank you for any suggestion.

The FOR DELIMITERS option treats each character as a delimiter. You cannot use a sequence of characters as a delimiter, so it will not help in your case.
Windows batch is a marginal text processor for simple tasks. You have a particularly nasty problem for a Windows batch file. It might be doable, but the code would be complicated and slow at best.
I strongly advise you use some other tool better suited for text processing. I believe any of the following could be used:
VBscript
JavaScript
Powershell
3rd party tools like Gnu sed for Windows, perl, ... many more
Windows batch is probably about the worst choice you could make, especially for your problem. (this is coming from someone who really enjoys using batch)

Related

String Substitution Using Variables in a FOR loop Batch Syntax

I am trying to use string substitution to truncate a list of full file paths down to relative paths in an existing text file. In addition there is some basic automated renaming. The renaming works as intended but the string substitution I cannot get to work. All the documentation I could find describing string substitution used standalone strings, not strings stored in variables. So I do not know and cannot figure out the proper syntax. I have also read similar questions asked here and attempted those solutions to no avail.
Everything in my script works as intended except for this line:
SET %%I=%%%I:%Temp_Dir%=%
It does not change %%I at all. The input to the FOR loop %List% is a text file that looks like this:
C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out\bin\codesegment.o
C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out\bin\graphic.o
C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out\bin\helper.o
C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out\bin\main.o
C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out\bin\game.out
C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out\bin
C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out
The final output I get right now is identical to the above list.
The desired output should look like this:
\out\bin\codesegment.o
\out\bin\graphic.o
\out\bin\helper.o
\out\bin\main.o
\out\bin\game.out
\out\bin
\out
I know the syntax is supposed to be:
SET string = C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working\out\bin\codesegment.o
SET string = %string:C:\Users\UserName\AppData\Local\Temp\Batch_Renaming_Temp\Working =%
As I said though, I cannot get it to work using variables in a FOR loop. I am also attempting this method of string substitution because the path of %Temp_Dir% is always at the start of every line and is always found in each line once.
Here is the whole script for reference. I would prefer a one line solution like the one I was attempting. I am aware longer solutions are available but due to reasons beyond my control the one-line string substitution is required.
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET Proj_Dir="C:\Users\UserName\Desktop\Project"
SET Temp_Dir=%temp%\Batch_Renaming_Temp\Working
SET Counter=1
SET List="%temp%\Batch_Renaming_Temp\LFN_LIST.TMP"
SET List_Final="%temp%\Batch_Renaming_Temp\LFN_LIST.TXT"
ROBOCOPY /E /MIR %Proj_Dir% "%Temp_Dir%" > NUL
CD "%Temp_Dir%"
DIR /A-D /O:N /B /S > %List%
DIR /AD /O:N /B /S | SORT /R >> %List%
TYPE NUL > %List_Final%
FOR /F "usebackq tokens=* delims=" %%I IN (%List%) DO (
REN "%%I" !Counter!
SET /A !Counter+=1
SET %%I=%%%I:%Temp_Dir%=%
ECHO %%I >> %List_Final%
)
Like #Squashman pointed out in the comments, you cannot "set" a FOR variable.
If your variable depends on other variables indirectly, you need to use CALL SET or delayed expansion.
The easiest solution so far:
(for /F "usebackq tokens=8* delims=\" %%x in (%List%) do echo \%%y) > %List_Final%
It uses \ as a delimiter and pass the 8+th arguments to %%y, and redirects all output to %List_Final%
Tested on a Windows 10 laptop, works perfectly.

bat file to replace string in text file

This question has been asked a lot on stackoverflow, but I can't seem to be able to make it work. Any hints appreciated. Here is a text file (extension .mpl) containing offending text that needs to be removed:
plotsetup('ps', 'plotoutput = "plotfile.eps"', 'plotoptions' = "color=rgb,landscape,noborder");
print(PLOT3D(MESH(Array(1..60, 1..60, 1..3, [[[.85840734641021,0.,-0.],
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],[.857971665313419,.0917163905694189,-.16720239349226],
... more like that ...
[.858407346410207,-3.25992468340355e-015,5.96532373555817e-015]]], datatype = float[8], order = C_order)),SHADING(ZHUE),STYLE(PATCHNOGRID),TRANSPARENCY(.3),LIGHTMODEL(LIGHT_4),ORIENTATION(35.,135.),SCALING(CONSTRAINED),AXESSTYLE(NORMAL)));
I want to remove every instance of:
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],
and there are thousands such instances!. Note: the square brackets and the comma are to be removed. There is no space, so I have pages and pages of:
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],
I won't list here all my failed attempts. Below is the closest I've come:
#echo off
SetLocal
cd /d %~dp0
if exist testCleaned.mpl del testCleaned.mpl
SetLocal EnableDelayedExpansion
Set OldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],
Set NewString=
pause
FOR /F "tokens=* delims= " %%I IN (test.mpl) DO (
set str=%%I
set str=!str:OldString=NewString!
echo !str! >> testCleaned.mpl
endlocal
)
EndLocal
The above was strung together, as it were, from pieces of code found on the web, especially at stackoverflow, e.g. Problem with search and replace batch file
What it does is produce a truncated file, as follows:
plotsetup('ps', 'plotoutput = "plotfile.eps"', 'plotoptions' = "color=rgb,landscape,noborder");
!str!
Please don't hesitate to request clarifications. Apologies if you feel that this question has already been answered. I would very much appreciate if you would copy-paste the relevant code for me, as I have tried for several hours.
Bonus: can this automatic naming be made to work? "%%~nICleaned.mpl"
The biggest problem with your existing code is the SetLocal enableDelayedExpansion is missplaced - it should be within the loop after set str=%%I.
Other problems:
will strip lines beginning with ;
will strip leading spaces from each line
will strip blank (empty) lines
will print ECHO is off if any lines becomes empty or contains only spaces after substitution
will add extra space at end of each line (didn't notice this until I read jeb's answer)
Optimization issue - using >> can be relatively slow. It is faster to enclose the whole loop in () and then use >
Below is about the best you can do with Windows batch. I auto named the output as requested, doing one better - It automatically preserves the extension of the original name.
#echo off
SetLocal
cd /d %~dp0
Set "OldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],"
Set "NewString="
set file="test.mpl"
for %%F in (%file%) do set outFile="%%~nFCleaned%%~xF"
pause
(
for /f "skip=2 delims=" %%a in ('find /n /v "" %file%') do (
set "ln=%%a"
setlocal enableDelayedExpansion
set "ln=!ln:*]=!"
if defined ln set "ln=!ln:%OldString%=%NewString%!"
echo(!ln!
endlocal
)
)>%outFile%
Known limitations
limited to slightly under 8k per line, both before and after substitution
search string cannot include = or !, nor can it start with * or ~
replacement string cannot include !
search part of search and replace is case insensitive
last line will always end with newline <CR><LF> even if original did not
All but the first limitation could be eliminated, but it would require a lot of code, and would be horrifically slow. The solution would require a character by character search of each line. The last limitation would require some awkward test to determine if the last line was newline terminated, and then last line would have to be printed using <nul SET /P "ln=!ln!" trick if no newline wanted.
Interesting feature (or limitation, depending on perspective)
Unix style files ending lines with <LF> will be converted to Windows style with lines ending with <CR><LF>
There are other solutions using batch that are significantly faster, but they all have more limitations.
Update - I've posted a new pure batch solution that is able to do case sensitive searches and has no restrictions on search or replacement string content. It does have more restrictions on line length, trailing control characters, and line format. Performance is not bad, especially if the number of replacements is low. http://www.dostips.com/forum/viewtopic.php?f=3&t=2710
Addendum
Based on comments below, a batch solution will not work for this particular problem because of line length limitation.
But this code is a good basis for a batch based search and replace utility, as long as you are willing to put up with the limitations and relatively poor performance of batch.
There are much better text processing tools available, though they are not standard with Windows. My favorite is sed within the GNU Utilities for Win32 package. The utilities are free, and do not require any installation.
Here is a sed solution for Windows using GNU utilities
#echo off
setlocal
cd /d %~dp0
Set "OldString=\[HFloat(undefined),HFloat(undefined),HFloat(undefined)\],"
Set "NewString="
set file="test.mpl"
for %%F in (%file%) do set outFile="%%~nFCleaned%%~xF"
pause
sed -e"s/%OldString%/%NewString%/g" <%file% >%outfile%
Update 2013-02-19
sed may not be an option if you work at a site that has rules forbidding the installation of executables downloaded from the web.
JScript has good regular expression handling, and it is standard on all modern Windows platforms, including XP. It is a good choice for performing search and replace operations on Windows platforms.
I have written a hybrid JScript/Batch search and replace script (REPL.BAT) that is easy to call from a batch script. A small amount of code gives a lot of powerful features; not as powerful as sed, but more than enough to handle this task, as well as many others. It is also quite fast, much faster than any pure batch solution. It also does not have any inherent line length limitations.
Here is a batch script that uses my REPL.BAT utility to accomplish the task.
#echo off
setlocal
cd /d %~dp0
Set "OldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],"
Set "NewString="
set file="test.txt"
for %%F in (%file%) do set outFile="%%~nFCleaned%%~xF"
pause
call repl OldString NewString le <%file% >%outfile%
I use the L option to specify a literal search string instead of a regular expression, and the E option to pass the search and replace strings via environment variables by name, instead of using string literals on the command line.
Here is the REPL.BAT utility script that the above code calls. Full documentation is encluded within the script.
#if (#X)==(#Y) #end /* Harmless hybrid line that begins a JScript comment
::************ Documentation ***********
:::
:::REPL Search Replace [Options [SourceVar]]
:::REPL /?
:::
::: Performs a global search and replace operation on each line of input from
::: stdin and prints the result to stdout.
:::
::: Each parameter may be optionally enclosed by double quotes. The double
::: quotes are not considered part of the argument. The quotes are required
::: if the parameter contains a batch token delimiter like space, tab, comma,
::: semicolon. The quotes should also be used if the argument contains a
::: batch special character like &, |, etc. so that the special character
::: does not need to be escaped with ^.
:::
::: If called with a single argument of /? then prints help documentation
::: to stdout.
:::
::: Search - By default this is a case sensitive JScript (ECMA) regular
::: expression expressed as a string.
:::
::: JScript syntax documentation is available at
::: http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx
:::
::: Replace - By default this is the string to be used as a replacement for
::: each found search expression. Full support is provided for
::: substituion patterns available to the JScript replace method.
::: A $ literal can be escaped as $$. An empty replacement string
::: must be represented as "".
:::
::: Replace substitution pattern syntax is documented at
::: http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx
:::
::: Options - An optional string of characters used to alter the behavior
::: of REPL. The option characters are case insensitive, and may
::: appear in any order.
:::
::: I - Makes the search case-insensitive.
:::
::: L - The Search is treated as a string literal instead of a
::: regular expression. Also, all $ found in Replace are
::: treated as $ literals.
:::
::: E - Search and Replace represent the name of environment
::: variables that contain the respective values. An undefined
::: variable is treated as an empty string.
:::
::: M - Multi-line mode. The entire contents of stdin is read and
::: processed in one pass instead of line by line. ^ anchors
::: the beginning of a line and $ anchors the end of a line.
:::
::: X - Enables extended substitution pattern syntax with support
::: for the following escape sequences:
:::
::: \\ - Backslash
::: \b - Backspace
::: \f - Formfeed
::: \n - Newline
::: \r - Carriage Return
::: \t - Horizontal Tab
::: \v - Vertical Tab
::: \xnn - Ascii (Latin 1) character expressed as 2 hex digits
::: \unnnn - Unicode character expressed as 4 hex digits
:::
::: Escape sequences are supported even when the L option is used.
:::
::: S - The source is read from an environment variable instead of
::: from stdin. The name of the source environment variable is
::: specified in the next argument after the option string.
:::
::************ Batch portion ***********
#echo off
if .%2 equ . (
if "%~1" equ "/?" (
findstr "^:::" "%~f0" | cscript //E:JScript //nologo "%~f0" "^:::" ""
exit /b 0
) else (
call :err "Insufficient arguments"
exit /b 1
)
)
echo(%~3|findstr /i "[^SMILEX]" >nul && (
call :err "Invalid option(s)"
exit /b 1
)
cscript //E:JScript //nologo "%~f0" %*
exit /b 0
:err
>&2 echo ERROR: %~1. Use REPL /? to get help.
exit /b
************* JScript portion **********/
var env=WScript.CreateObject("WScript.Shell").Environment("Process");
var args=WScript.Arguments;
var search=args.Item(0);
var replace=args.Item(1);
var options="g";
if (args.length>2) {
options+=args.Item(2).toLowerCase();
}
var multi=(options.indexOf("m")>=0);
var srcVar=(options.indexOf("s")>=0);
if (srcVar) {
options=options.replace(/s/g,"");
}
if (options.indexOf("e")>=0) {
options=options.replace(/e/g,"");
search=env(search);
replace=env(replace);
}
if (options.indexOf("l")>=0) {
options=options.replace(/l/g,"");
search=search.replace(/([.^$*+?()[{\\|])/g,"\\$1");
replace=replace.replace(/\$/g,"$$$$");
}
if (options.indexOf("x")>=0) {
options=options.replace(/x/g,"");
replace=replace.replace(/\\\\/g,"\\B");
replace=replace.replace(/\\b/g,"\b");
replace=replace.replace(/\\f/g,"\f");
replace=replace.replace(/\\n/g,"\n");
replace=replace.replace(/\\r/g,"\r");
replace=replace.replace(/\\t/g,"\t");
replace=replace.replace(/\\v/g,"\v");
replace=replace.replace(/\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}/g,
function($0,$1,$2){
return String.fromCharCode(parseInt("0x"+$0.substring(2)));
}
);
replace=replace.replace(/\\B/g,"\\");
}
var search=new RegExp(search,options);
if (srcVar) {
WScript.Stdout.Write(env(args.Item(3)).replace(search,replace));
} else {
while (!WScript.StdIn.AtEndOfStream) {
if (multi) {
WScript.Stdout.Write(WScript.StdIn.ReadAll().replace(search,replace));
} else {
WScript.Stdout.WriteLine(WScript.StdIn.ReadLine().replace(search,replace));
}
}
}
The Batch file below have the same restrictions of previous solutions on characters that can be processed; these restrictions are inherent to all Batch language programs. However, this program should run faster if the file is large and the lines to replace are not too much. Lines with no replacement string are not processed, but directly copied to the output file.
#echo off
setlocal EnableDelayedExpansion
set "oldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],"
set "newString="
findstr /N ^^ inFile.mpl > numberedFile.tmp
find /C ":" < numberedFile.tmp > lastLine.tmp
set /P lastLine=<lastLine.tmp
del lastLine.tmp
call :ProcessLines < numberedFile.tmp > outFile.mpl
del numberedFile.tmp
goto :EOF
:ProcessLines
set lastProcessedLine=0
for /F "delims=:" %%a in ('findstr /N /C:"%oldString%" inFile.mpl') do (
call :copyUpToLine %%a
echo(!line:%oldString%=%newString%!
)
set /A linesToCopy=lastLine-lastProcessedLine
for /L %%i in (1,1,%linesToCopy%) do (
set /P line=
echo(!line:*:=!
)
exit /B
:copyUpToLine number
set /A linesToCopy=%1-lastProcessedLine-1
for /L %%i in (1,1,%linesToCopy%) do (
set /P line=
echo(!line:*:=!
)
set /P line=
set line=!line:*:=!
set lastProcessedLine=%1
exit /B
I would appreciate if you may run a timing test on this an other solutions and post the results.
EDIT: I changed the set /A lastProcessedLine+=linesToCopy+1 line for the equivalent, but faster set lastProcessedLine=%1.
I'm no expert on batch files, so I can't offer a direct solution to your problem.
However, to solve your problem, it might be simpler to use an alternative to batch files.
For example, I'd recommend using http://www.csscript.net/ (if you know C#). This tool will allow you to run C# files like batch files, but giving you the power to write your script using C#, instead of horrible batch file syntax :)
Another alternative would be python, if you know python.
But I guess the point is, that this kind of task may be easier in another programming language.
You defined delims=<space>, that's a bad idea if you want to preserve your lines, as it splits after the first space.
You should change this to FOR /F "tokens=* delims=" ....
Your echo !str! >> testCleaned.mpl will always append one extra space to each line, better use echo(!str!>>testCleaned.mpl.
You will also lose all empty lines, and all exclamation marks in all lines.
You could also try the code of Improved BatchSubstitute.bat

String processing using Batch Script

I'm currently creating a batch script that has to loop through the lines in a file, checking for some string, and if theres a match prefix that string with a '#' (comment it out).
I'm perfectly new to batch script, all I got this far is:
for /f %%j in (CMakeLists.txt) do (
if "%%j"=="Extensions_AntTweakBar" (
echo lol1
)
if "%%j"=="Extensions_Inspection" (
echo lol2
)
if "%%j"=="Extensions_InspectionBar" (
echo lol3
)
)
So my current issue is, I don't know how to operate on string within batch scripts. If someone could help me out that would be appreciated :)
You can just use the text you want to append followed by your variable generally.
C:\>set MY_VAR=Hello world!
C:\>echo #%MY_VAR%
#Hello world!
C:\>set MY_VAR=#%MY_VAR%
C:\>echo %MY_VAR%
#Hello world!
If you're just doing echo, that's fine. echo #%%j will do what you need.
But if you want to set the line to a variable, you have to enable delayed expansion. Add setlocal ENABLEDELAYEDEXPANSION to the top of your file and then surround your variables with ! instead of %. For example (and notice that I've added delims= to put the entire line in %%j instead of the first word on the line):
#echo off
setlocal ENABLEDELAYEDEXPANSION
set LINE=
for /f "delims=" %%j in (CMakeLists.txt) do (
set LINE=%%j
if "%%j"=="Extensions AntTweakBar" (
set LINE=#%%j
)
if "%%j"=="Extensions Inspection" (
set LINE=#%%j
)
if "%%j"=="Extensions InspectionBar" (
set LINE=#%%j
)
echo !LINE!
)
Given this input file:
Extensions AntTweakBar
some text
Extensions Inspection
Extensions What?
some more text
Extensions InspectionBar
Extensions InspectionBar this line doesn't match because delims= takes all text
even more text
The above script produces this output:
C:\>comment.bat
#Extensions AntTweakBar
some text
#Extensions Inspection
Extensions What?
some more text
#Extensions InspectionBar
Extensions InspectionBar this line doesn't match because delims= takes all text
even more text
And of course removing #echo off will help you debug problems.
But all that being said, you're about at the limit of what you can accomplish with batch string processing. If you still want to use batch commands, you may need to start writing lines to temporary files and using findstr with a regex.
Without a better understanding of what you want inside your loop or what your CMakeLists.txt file looks like, try this on for starters:
FINDSTR "SOMETHING" %%J && ECHO #%%J || ECHO %%J
The && makes the second command (the ECHO) conditional on the first command exiting without an error state, and the || is like a logical OR and it runs when the first one doesn't.
Really, for modifying the internals of a text file you are probably going to be much better off using either sed or awk - win32 binaries can be found in the UnxUtils project.

Escaping an equals sign in DOS batch string replacement command

I need to replace some text in a JNLP file using a DOS batch file to tune it for the local machine.
The problem is that the search pattern contains an equals sign which is messing up the string replacement in the batch file.
I want to replace the line,
<j2se version="1.5" initial-heap-size="100M" max-heap-size="100M"/>
with specific settings for the initial and max heap sizes.
For example at the moment I have,
for /f "tokens=* delims=" %%a in (%filePath%agility.jnlp) do (
set str=%%a
set str=!str:initial-heap-size="100M"=initial-heap-size="%min%M"!
echo !str!>>%filePath%new.jnlp)
but the = in the search pattern is being read as part of the replacement command.
How do I escape the equals sign so it is processed as text?
The best solution is to download and install Cygwin or GNUWin32 but, if you're really limited to the standard command processor, it can get a little messy.
This is not the fastest method in the world but it's at least functional. This command file processes each line one character at a time, treating specially the case where you find the stanza you're looking for.
#echo off
set init=50M
set max=75M
setlocal enableextensions enabledelayedexpansion
for /f "tokens=* delims=" %%a in (agility.jnlp) do (
set str1=%%a
call :morph
echo !str2!>>agility_new.jnlp
echo !str2!
)
endlocal
goto :eof
:morph
set str2=
:morph1
if not "x!str1!"=="x" (
if "!str1:~0,18!"=="initial-heap-size=" (
set str2=!str2!initial-heap-size="!init!"
set str1=!str1:~24!
goto :morph1
)
if "!str1:~0,14!"=="max-heap-size=" (
set str2=!str2!max-heap-size="!max!"
set str1=!str1:~20!
goto :morph1
)
set str2=!str2!!str1:~0,1!
set str1=!str1:~1!
goto :morph1
)
goto :eof
With the input file:
<j2se version="1.5" initial-heap-size="100M" max-heap-size="100M"/>
next line
===
you end up with:
<j2se version="1.5" initial-heap-size="50M" max-heap-size="75M"/>
next line
===
One cannot simply replace (a substring with) an equal-sign, without splitting up (for-statement with "delims==") or trimming…
But perhaps you could go for this simpler but more confusing approach, using the following statement in your for-loop:
set str=!str:"100M" max-heap-size="%min%M" max-heap-size!
It just combines the string to replace with what comes after instead of what comes before, avoiding any equal-sign replacements entirely.
If you can pass the arguments as something else, such as double underscores, you can iterate through them and convert them to '=' in the batch file.
#rem Replace __ with = in batch files.
#rem This works around the lack of equals signs in args
#rem args contains full args string with substitutions in place
setlocal enabledelayedexpansion
:argloop
if "%~1" NEQ "" (
set str=%~1
set out=!str:__==!
set %~1=!out!
set args=!args!!out!
SHIFT
goto :argloop
)
#rem Can now run program on a line on its own with just %args%
Source: https://github.com/mlabbe/batchargs
Here's an alternative solution. If you can afford to download GNU tools, you can use sed:
C:\test>set a=200
C:\test>sed -i.bak "s/^\(.*initial-heap-size=\"\).*\( max.*\)/\1%a%\"\2/" file

How to do this in PowerShell? Or : what language to use for file and string manipulation?

What language should I use for file and string manipulation?
This might seem objective, but really isn't I think. There's lot to say about this. For example I can see clearly that for most usages Perl would be a more obvious candidate than Java. I need to do this quite often and at this time I use C# for it, but I would like a more scriptlike language to do this.
I can imagine Perl would be a candidate for it, but I would like to do it in PowerShell since PowerShell can access the .NET library (easy). Or is Python a better candidate for it? If I have to learn a new language, Python is certainly one on my list, rather than Perl.
What I want to do for example, is to read a file, make some changes and save it again. E.g.: open it, number all lines (say with 3 digits) and close it.
Any example, in any language, would be welcome, but the shorter the better. It is utility scripting I'm after here, not OO, TDDeveloped, unit-tested stuff of course.
What I would very much like to see is something as (pseudocode here):
open foobar.as f
foreach line in f.lines
line.addBefore(currenIteratorCounter.format('ddd') + '. ')
close f
So:
bar.txt
Frank Zappa
Cowboy Henk
Tom Waits
numberLines bar.txt
bar.txt
001. Frank Zappa
002. Cowboy Henk
003. Tom Waits
UPDATE:
The Perl and Python examples here are great, and definitely in the line of what I was hoping and expecting. But aren't there any PowerShell guys out there?
This is actually pretty easy in PowerShell:
function Number-Lines($name) {
Get-Content $name | ForEach-Object { $i = 1 } { "{0:000}. {1}" -f $i++,$_ }
}
What I'm doing here is getting the contents of the file, this will return a String[], over which I iterate with ForEach-Object and apply a format string using the -f operator. The result just drops out of the pipeline as another String[] which can be redirected to a file if needed.
You can shorten it a little by using aliases:
gc .\someFile.txt | %{$i=1}{ "{0:000}. {1}" -f $i++,$_ }
but I won't recommend that for a function definition.
You way want to consider using two passes, though and constructing the format string on the fly to accommodate for larger numbers of lines. If there are 1500 lines {0:000} it won't be sufficient anymore to get neatly aligned output.
As for which language is best for such tasks, you might look at factors such as
conciseness of code (Perl will be hard to beat there, especially that one-liner in another answer)
readability and maintainability of code
availability of the tools (Perl and Python aren't installed on Windows by default (PowerShell only since Windows 7), so deployment might be hindered.)
In the light of the last point you might even be better off using cmd for this task. The code is similarly pretty simple:
#echo off
setlocal
set line=1
for /f "delims=" %%l in (%1) do call :process %%l
endlocal
goto :eof
:process
call :lz %line%
echo %lz%. %*
set /a line+=1
goto :eof
:lz
if %1 LSS 10 set lz=00%1&goto :eof
if %1 LSS 100 set lz=0%1&goto :eof
set lz=%1&goto :eof
goto :eof
That assumes, of course, that it has to run somewhere else than your own machine. If not, then use whatever fits your needs :-)
perl -i -ne 'printf("00%d. %s",$.,$_)' your-filename-here
You may want %03d instead.
It isn't what you wanted, but please recall findstr.exe(and find.exe) at times...
findstr /n ".*" filename
find "" /v /n filename
Python
target = open( "bar_with_numbers.txt", "w" )
source = open( "bar.txt", "r" )
for count, line in enumerate( source ):
target.write( "%3d. %s\n" % ( count+1, line ) )
source.close()
target.close()
First, it's a bad policy to "update" files in place. In the long run, this becomes a regrettable decision because debugging is made harder by the loss of history.
If you use OS redirection features, this program can be simplified.
import sys
for count, line in enumerate( sys.stdin ):
sys.stdout.write( "%3d. %s\n" % ( count+1, line ) )
Then you can run this enumerate.py as follows
python enumerate.py <bar.txt >bar_with_numbers.txt
More importantly, you can also do this.
python enumerate.py <bar.txt | the_next_step
On a Debian system (and probably other linux distros) you could do this:
$ nl -w 3 -n rz -s ". " [filename] > [newfilename]
Definitely Perl. It supports inline replacement (on Windows you have to start the script with perl .i.bak (because Windows cannot do this inline and creates a .bak file with the same name.)
open(IN,'+>'.$yourfile) || die "Can not open file $yourfile: $!";
my $line_no = 1;
while(<IN>){
print "$line_no. $_";
$line_no++;
}
close IN;
Code just typed from memory without testing. But that should work.
You probably want to add some logic for formatting $line_no (e.g. first count lines and then add as much zero digits as you need.)

Resources