Archive: delete lines in a file by line number?


delete lines in a file by line number?
  I understand how to search for a string in a file and replace all occurrence of that string, but i need to do something a little different. Here is exactly what I need to do:

Read text file and look for a specific string on line four. If string matches, delete lines 3-20.

I cannot loop through the entire file because several of the lines I need to delete are in several places later in the file. I need to delete the first occurrence of them only.

Thanks


FileRead returns line by line, or if the line is too long NSIS_MAX_STRLEN chars of that line which is usually 1024 unless you've recompiled NSIS with a different value.

Do you have any specific problems scripting this?


yeah, actually doing it. It only seems to work if I loop through the entire file looking for the string. Since the string I am looking for occurs multiple times, I end up deleting all occurrences of it instead of just the first one.

Like I said, all I need to do is see if a particular string exists in the file. If it does, I need to delete 18 specific lines. The problem is that several of those lines are not unique. The only unique identifier is that those 18 lines will always be in the same place in the file. Lines 3 through 20.

Looping through the file and replacing every occurrence is a piece of cake, I just can't figure out how to replace only the first occurrences. I thought it would just be a matter of modifying a something like this, but I can't get it to work without looping throught the entire file and replacing everything:


FileOpen $0 "temp.txt" r
GetTempFileName $R0
FileOpen $1 $R0 w
loop:
FileRead $0 $2
IfErrors done
StrCmp $2 "<name>Unassociated Certificates</name>$\r$\n" 0 +3
FileWrite $1 "$\r$\n"
goto loop
FileWrite $1 $2
Goto loop

done:
FileClose $0
FileClose $1
Delete "temp.xml"
CopyFiles /SILENT $R0 "temp.xml"
Delete $R0


beginning of temp.txt:


<?xml version="1.0" encoding="UTF-8"?>
<dataroot xmlns:od="urn:schemas-microsoft-com:officedata">
<address>
<name>Unassociated Certificates</name>
<number>-1</number>
<automate>0</automate>
<contenttype>0</contenttype>
<encoding>0</encoding>
<security>0</security>
<envelope>0</envelope>
<sign>0</sign>
<encryptalgorithm>0</encryptalgorithm>
<keysize>0</keysize>
<digestalgorithm>0</digestalgorithm>
<subject>default</subject>
<includedtext>default</includedtext>
<filename>default.edi</filename>
<email>nc923y54hd2fd34@n-f102nd1434sxs44</email>
<smtp>DEFAULT</smtp>
</address>



If <name>Unassociated Certificates</name> exists, I need to delete the first XML section <address>...</address>. Maybe this is can't be done with NSIS. My brain is fried.


An idea: instead of saving just he last read line, save the two last. If the line you've just read is "<name>..." don't write the next 16 lines, and the two lines you've just read. Use IntOp to accomplish this (IntOp $0 $0 - 1 for example, then if $0 is not 0 don't write the line). If you've not found the line write the last line you've read and move the line you've just read to the variable saving the last read line.

Makes sense?


Hmmm, I understand exactly what you are saying...that is exactly what I want to do. I want to stop as soon as the string is found and skip writing the current line, the line before, and the next 16 lines. I know what I want to do, I just don't know how to do it. I'm not a programmer, but this just does't seem like that complicated of a thing. Even something like counting the number of lines in the file and not writing certain line numbers would be sufficient. My goal is to avoid having the user edit the file manually. Maybe I am just missing something totally obvious...


Well, I was trying to guide you to something like this:


FileOpen $0 "temp.xml" r

GetTempFileName $R0
FileOpen$1 $R0 w

FileRead$0 $2
Push$2
Call TrimNewLines
Pop$2

StrCpy$4 0

loop:
ClearErrors
FileRead$0 $3
IfErrors done
Push$3
Call TrimNewLines
Pop$3
StrCmp$3 "<name>Unassociated Certificates</name>" 0 +2
StrCpy$4 18
StrCmp$4 0 0 skipWrite
FileWrite$1 "$2$\r$\n"
skipWrite:
StrCpy $2 $3
StrCmp$4 0 +2
IntOp$4 $4 - 1
Goto loop

done:
StrCmp $4 0 0 +2
FileWrite$1 "$2$\r$\n"
FileClose $0
FileClose$1
Delete "temp.xml"
CopyFiles /SILENT $R0 "temp.xml"
Delete $R0
>
Get TrimNewLines from the docs.

Thanks much, that works like a charm. I'm just wanting to understand what that code is actually doing:

FileOpen $0 "temp.xml" r ;open the original file into $0
GetTempFileName $R0 ;assign a temp file to $R0
FileOpen $1 $R0 w ;open the temp file into $1

FileRead $0 $2 ;read first file up to first line break into $2
Push $2 ;this one confuses me, where is $2 being pushed to
Call TrimNewLines ;i assume this is removing line breaks from $2
Pop $2 - confused again. i guess $2 is pushed for TrimNewLines and the function result is popped back into $2?

StrCpy $4 0 ;really confused here, what is $4? Is $4 now 0?

loop:
ClearErrors
FileRead $0 $3 ;reading original file again into $3
IfErrors done ;if nothing left to read go to done
Push $3 ;exchange $3 with $2??
Call TrimNewLines ;remove line breaks from $3
Pop $3 ;not sure
StrCmp $3 "<name>Unassociated Certificates</name>" 0 +2 ;
StrCpy $4 18 ;again, what is $4, what is 18, does $4=18 now?
StrCmp $4 0 0 skipWrite ; compare $0 with 0? totally lost here
FileWrite $1 "$2$\r$\n"
skipWrite:
StrCpy $2 $3
StrCmp $4 0 +2
IntOp $4 $4 - 1
Goto loop


You've got them all right. Push pushes a value into the stack for TrimNewLines to read, and Pop pops the return value of TrimNewLines out of the stack and into $2/$3. StrCpy does copy the value on the right to the variable on the left, so StrCpy $4 0 sets $4 to 0. StrCmp $4 0 0 skipWrite compares $4 to 0 (actually "0"), if equal just continues (0 means continue, no jump) and if not it jumps to skipWrite.