ReplaceInFile

From NSIS Wiki
Jump to navigationJump to search
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.