如何从Delphi中的文本文件中删除特定行

时间:2016-08-18 15:10:31

标签: delphi text-files delphi-7

我有一个文本文件,其中逐行存储了用户信息。每行的格式为:svnadmin load,其中“#”为分隔符。

我尝试使用此编码来执行任务:

UserID#UserEmail#UserPassword

但我不确定在“索引”空间放什么。

有什么方法可以接收用户ID作为输入,然后从具有此用户ID的文本文件中删除文本行?任何帮助将不胜感激。

6 个答案:

答案 0 :(得分:6)

您可以将NameValueSeparator设置为#,然后使用IndexOfName查找用户,只要用户名是文件中的第一个值。

sl.NameValueSeparator := '#';
Index := sl.IndexOfName('455115')

所以在你的例子中,就像这样

var sl:TStringList;
begin
  sl:=TStringList.Create;
  sl.LoadFromFile('filename');
  sl.NameValueSeparator := '#';
  Index := sl.IndexOfName('455115')
  if (Index  <> -1) then
  begin
      sl.Delete(Index);
      sl.SaveToFile('filename');
  end;
  sl.free;
end;

对于大型文件,这可能会很慢,因为IndexOfName会循环遍历TStringList中的每一行,并依次检查每个字符串,直到找到匹配为止。

免责声明:测试/使用Delphi 2007,Delphi 7可能不同。

答案 1 :(得分:1)

我不明白为什么这么多人如此努力。这很简单:

function ShouldDeleteLine(const UserID, Line: string): Boolean;
begin    
  // Remember: Pos(Needle, Haystack)
  Result := Pos(UserID + '#', Line) = 1; // always 1-based!
end;

procedure DeleteLinesWithUserID(const FileName, UserID: string);
var
  SL: TStringList;
  I: Integer;
begin
  if not FileExists(FileName) then
    Exit;

  SL := TStringList.Create;
  try
    SL.LoadFromFile(FileName); // Add exception handling for the 
                               // case the file does not load properly.

    // Always work backward when deleting items, otherwise your index
    // may be off if you really delete.
    for I := SL.Count - 1 downto 0 do
      if ShouldDeleteLine(SL[I], UserID) then
      begin
        SL.Delete(I);
        // if UserID is unique, you can uncomment the following line.
        // Break;
      end;
    SL.SaveToFile(FileName);
  finally
    SL.Free;
  end;
end;

正如Arioch所说,如果您保存到相同的文件名,则在保存失败时可能会丢失数据,因此您可以执行类似

的操作
SL.SaveToFile(FileName + '.dup');
if FileExists(FileName + '.old') then
  DeleteFile(FileName + '.old');
RenameFile(FileName, FileName + '.old');
RenameFile(FileName + '.dup', FileName);

将原始文件的备份保留为FileName + '.old'

说明

向后工作

为什么要倒退?因为如果您有以下项目

A B C D E F G
      ^

您删除^处的项目,然后以下项目将向下移动:

A B C E F G
      ^

如果你向前迭代,你现在将指向

A B C E F G
        ^
从不检查

E。如果你倒退,那么你会指出:

A B C E F G
    ^

请注意,我已经检查了EFG,所以现在您确实会检查下一个项目C,并且您不会错过任何。此外,如果您使用0 to Count - 1向上移动,并且删除,Count将减少一个,最后,您将尝试访问列表的边界。如果您使用Count - 1 downto 0向后工作,则无法执行此操作。

使用+ '#'

如果您附加'#'并测试Pos() = 1,您将确保将整个UserID捕获到分隔符,而不是仅具有包含您正在寻找的UserID。 IOW,如果UserID'velthuis',则您不想删除'rudyvelthuis#rvelthuis01#password''velthuisresidence#vr#password2'等行,但您确实要删除'velthuis#bla#pw3'

E.g。在查找用户名时,您会出于同样的原因查找'#' + UserName + '#'

答案 2 :(得分:0)

实际上“从文本文件中删除一行”是唯一的方法 - 即创建一个包含更改内容的新文件,然后重新编写它。

所以你最好明确地做。

你不要忘记防止错误。如果发生任何错误,您当前的代码可能只会破坏文件并泄漏内存......

var sl: TStringList;
    s, prefix: string;
    i: integer; okay: Boolean;
    fs: TStream;

begin
  prefix := 'UserName' + '#';
  okay := false;

  fs := nil;
  sl:=TStringList.Create;
  Try   /// !!!!
    sl.LoadFromFile('filename');
    fs := TFileStream.Create( 'filename~new', fmCreate or fmShareExclusive );

    for i := 0 to Prev(sl.Count) do begin
      s := sl[ i ];

      if AnsiStartsStr( prefix, Trim(s) ) then
         continue;  // skip the line - it was our haunted user

      s := s + ^M^J;  // add end-of-line marker for saving to file

      fs.WriteBuffer( s[1], length(s)*SizeOf(s[1]) );  
    end; 
  finally 
    fs.Free;
    sl.Free;
  end;

  // here - and only here - we are sure we successfully rewritten 
  // the fixed file and only no are able to safely delete old file
  if RenameFile( 'filename' , 'filename~old') then
     if RenameFile( 'filename~new' , 'filename') then begin
        okay := true; 
        DeleteFile( 'filename~old' ); 
     end;

  if not okay then ShowMessage(' ERROR!!! ');
end;

注1:查看用户名检查是区分大小写还是忽略大小写:

注2:在Delphi 7中,SizeOf( s[1] )始终等于1,因为stringAnsiString的别名。但在较新的Delphi版本中却没有。这可能看起来很乏味和多余 - 但它可能会在未来挽救很多头痛。更好的方法是拥有一个像AnsiString

这样的临时a := AnsiString( s + ^m^J ); fs.WriteBuffer(a[1],Length(a));类型变量

答案 3 :(得分:0)

到目前为止,每个人都建议使用 For..Then 循环,但我可以建议重复......而

传统的 For..Loop 是一个不错的选择,但如果你有一长串的用户名(它们通常是唯一的),效率可能会很低。找到并删除后, For 循环将一直持续到列表结尾。如果您有一个小列表但是如果您有500,000个用户名并且您想要的那个位于10,000,则没有理由继续超过该点。

因此,试试这个。

ValueError: cannot reindex or align along dimension 'x' because the index has duplicate values

一旦调用,该函数返回True或False,表示用户名已删除。

    Function DeleteUser(Const TheFile: String; Const TheUserName: String): Boolean;
    Var
      CurrentLine: Integer;
      MyLines: TStringlist;
      Found: Boolean;
      Eof: Integer;

    Begin

      MyLines := TStringlist.Create;
      MyLines.LoadFromFile(TheFile);

      CurrentLine := 0;
      Eof := Mylines.count - 1; 

      Found := false;

      Repeat 

        If Pos(UpperCase(TheUserName), UpperCase(MyLines.Strings[CurrentLine])) = 1 Then
        Begin

         MyLines.Delete(CurrentLine);
          Found := True;

        End;

        Inc(CurrentLine);

      Until (Found) Or (CurrentLine = Eof); // Jump out when found or End of File

      MyLines.SaveToFile(TheFile);
      MyLines.Free;

      result := Found;
    End;

答案 4 :(得分:0)

只是为了好玩,这是一个紧凑的解决方案,我喜欢它的可读性。

const fn = 'myfile.txt';

procedure DeleteUser(id: integer);
var s:string; a:TStringDynArray;
begin
  for s in TFile.ReadAllLines(fn) do
    if not s.StartsWith(id.ToString + '#') then
      a := a + [s];

  TFile.WriteAllLines(fn, a);
end;

显然,这不是最有效的解决方案。通过不向数组附加单个项目或通过缓存搜索字符串,可以更快地运行。

要搜索其他字段,您可以使用s.split(['#'])[0]查找用户名,s.split(['#'])[1]查找电子邮件等。

答案 5 :(得分:0)

对于那些喜欢单行的人。这也有效:

const fn = 'users.txt';

procedure DeleteUserRegExp(id: string);
begin
  TFile.WriteAllText(fn,TRegEx.Replace(TFile.ReadAllText(fn),''+id+'\#.*\r\n',''))
end;

<强>解释

  1. 它将文件的内容加载到字符串中。
  2. 该字符串将发送至TRegEx.Replace
  3. 正则表达式搜索用户名,后跟哈希符号,然后搜索任何字符,然后搜索CRLF。它用空字符串替换它。
  4. 然后将生成的字符串写入原始文件
  5. 这只是为了好玩,因为我看到了很长的代码,我认为只需一行代码即可实现。