ReplaceInFile
Author: Datenbert (talk, contrib) |
Description
This is a simple way to replace the occurrences of a certain string throughout the lines of a file. This I use to turn templates for configuration files into 'real' ones, but for sure you may replace everything.
For your convenience, I suggest you save the following macro and the function together in one file as ${NSISDIR}\Include\ReplaceInFile.nsh and simply include it somewhere in your script along with an !include for StrRep.nsh containing the function StrRep, which is used by the script. StrRep can be found in this archive, too.
Please find an example below the code shown beneath.
Usage
!include StrRep.nsh !include ReplaceInFile.nsh [...] !insertmacro _ReplaceInFile SOURCE_FILE SEARCH_TEXT REPLACEMENT
You are not forced to use the provided macro, but I find it very convenient to call the function through a macro, as it's just one single line then.
The Macro
!macro _ReplaceInFile SOURCE_FILE SEARCH_TEXT REPLACEMENT Push "${SOURCE_FILE}" Push "${SEARCH_TEXT}" Push "${REPLACEMENT}" Call RIF !macroend
The Function
Function RIF ClearErrors ; want to be a newborn Exch $0 ; REPLACEMENT Exch Exch $1 ; SEARCH_TEXT Exch 2 Exch $2 ; SOURCE_FILE Push $R0 ; SOURCE_FILE file handle Push $R1 ; temporary file handle Push $R2 ; unique temporary file name Push $R3 ; a line to sar/save Push $R4 ; shift puffer IfFileExists $2 +1 RIF_error ; knock-knock FileOpen $R0 $2 "r" ; open the door GetTempFileName $R2 ; who's new? FileOpen $R1 $R2 "w" ; the escape, please! RIF_loop: ; round'n'round we go FileRead $R0 $R3 ; read one line IfErrors RIF_leaveloop ; enough is enough RIF_sar: ; sar - search and replace Push "$R3" ; (hair)stack Push "$1" ; needle Push "$0" ; blood Call StrReplace ; do the bartwalk StrCpy $R4 "$R3" ; remember previous state Pop $R3 ; gimme s.th. back in return! StrCmp "$R3" "$R4" +1 RIF_sar ; loop, might change again! FileWrite $R1 "$R3" ; save the newbie Goto RIF_loop ; gimme more RIF_leaveloop: ; over'n'out, Sir! FileClose $R1 ; S'rry, Ma'am - clos'n now FileClose $R0 ; me 2 Delete "$2.old" ; go away, Sire Rename "$2" "$2.old" ; step aside, Ma'am Rename "$R2" "$2" ; hi, baby! ClearErrors ; now i AM a newborn Goto RIF_out ; out'n'away RIF_error: ; ups - s.th. went wrong... SetErrors ; ...so cry, boy! RIF_out: ; your wardrobe? Pop $R4 Pop $R3 Pop $R2 Pop $R1 Pop $R0 Pop $2 Pop $0 Pop $1 FunctionEnd
There's no need for you to call the function itself, but you could, if you felt to. A possible situation would be a script that builds together (or leaves behind...) a stack containing the needed items. In a case like that it would be much easier to directly call the function after perhaps having just rearranged the stack. It's up to you - leaving the macro aside will surely do no harm, but unnecessarily enlargen your scripts when making heavy use of RIF.
Another use is setting RIF as a creator function of one or all of your custom pages. The preceding page could leave a result file to tweak on the stack, which the following page may parse to look what has beend done already. This way you'll be able to create a multiplexing page system that doesn't matters about the correct (custom) page order, as long as each page knows what has to be done by other pages before it is allowed to execute, which page has to be called in case it does fail to execute and which if it doesn't. You get the idea.
NSIS allows you to give a function the very name a macro has been assigned before - NSIS recognizes them as the separate and correctly configured objects because of their types! I nevertheless chose a short name for the main function here to keep the code shorter. If you replace every occurrence of RIP with ReplaceInFile, your code would look much more geekish. ;)
Example
Imagine a configuration file example.conf having a line
ServerRoot "�PLACEHOLDER_ROOTbin\"
Well, it could also be the line is
ServerRoot "�PLACEHOLDER_ROOTbin-test\" # check!
That does not matter - only �PLACEHOLDER_ROOT gets replaced throughout the whole file, even if it occurs more than one time on one single line. To replace �PLACEHOLDER_ROOT with "c:\program files\my server app\", you'd simply have to insert this in your code:
!insertmacro _ReplaceInFile "example.conf" "�PLACEHOLDER_ROOT" "C:\program files\my server app\"
Afterwards, the two example.conf will look like this:
ServerRoot "C:\program files\my server app\bin\" ServerRoot "C:\program files\my server app\bin-test\" # check!
Now, imagine having a bunch of different occurrences of such variables or otherwise exchangable strings. A simple list of calls to the macro _ReplaceInFile and you're set.
Caveats
You can easily produce infinite loops, as the function loops through the current line again after each replace operation to not miss any additional occurrences of the search string on the very line. If you replace the found string with something containing a variable expanding to something containing the search string, you'd build up an ever increasing line which surely will blow up the installer sometime. Reread the above, if you don't get through at once.
The second Caveat resembles the above, but is a much more tricky one. Imagine having no special replace pattern. In example2.conf you have
ClientDir "Client\solo\solo"
You now want to replace the doubled \solo by using this:
!insertmacro _ReplaceInFile "example2.conf" "Client\solo" "Client"
This code will *not* produce what you'd at first expect, as two replacements will take place, even if there is only one "Client\solo" in it: The first replacement will "produce" a new one:
ClientDir "Client\solo"
The second loop will then shorten it to the final
ClientDir "Client"
Surely not what you've expected it to be, hm? Use one of these commands instead:
!insertmacro _ReplaceInFile "example2.conf" "Client\solo\solo" "Client\solo" !insertmacro _ReplaceInFile "example2.conf" "solo\solo" "solo"
The third Caveat appears when you replace several strings in the same file where the search strings do not follow a common format. An example would be to replace "..\" throughout a file - you might replace something you just do not want to. It is always safe to actually construct templates holding patterns that cannot appear in another context of the template.
For example, you may not have such a URL: http://)))TIKI-TAKA(((/cgi-bin/bin/ - simply replace the certainly unique )))TIKI-TAKA((( with something senseful: http://www.example.com/cgi-bin/bin/. For sure you may choose whichever pattern you like - you get the idea, again.
The fourth point to look at when using ReplaceInFile is that the backupped file ${SOURCE_FILE}.old gets deleted without notice, even if it wasn't ReplacInFile that created it. Take care to not have a file like that.
Additionally, the script does very little error checking. It SetErrors if the file to question is missing or otherwise not accessible - that's it. The routine will ClearErrors prior to ending, ehrm...
To mention the good things - it does neither destroy the stack nor your variable structure. Hopefully ;-)
You may contact me via my homepage http://robertkehl.de/ if you feel you should. May you also have a peek at http://roknet.de/ if you're interested in professional IT services, including install scripts for your apps.
Have fun using your NSIS,
Robert Kehl
Bf:Minor change needed
Just a minor point: the StrRep function in the Wiki now is called StrRep rather than StrReplace so you'll need to alter
call StrReplace ; do the bartwalk
to instead read
Call StrRep ;; do the bartwalk, whatever that may be!
If you don't your install script fails to compile with a message about StrReplace not found.
JohnBurgess.