【问题标题】:how to replace string in file using msbuild?如何使用 msbuild 替换文件中的字符串?
【发布时间】:2011-12-11 20:46:32
【问题描述】:

我想用另一个文件xy.xml中的字符串“我很好”替换文件test.xml中的字符串“我很好”。在ms build中使用正则表达式。

我必须从一个文件(xy.xml) 中读取字符串并将其替换为另一个文件test.xml。 所以请提供必要的步骤来通过示例解决这个问题

【问题讨论】:

    标签: msbuild msbuild-task msbuild-propertygroup msbuild-buildengine


    【解决方案1】:

    这不再需要...您现在可以将 C# 注入项目/构建文件...

    定义自定义任务和参数如下:

    <UsingTask TaskName="ReplaceFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
      <ParameterGroup>
        <InputFilename ParameterType="System.String" Required="true" />
        <OutputFilename ParameterType="System.String" Required="true" />
        <MatchExpression ParameterType="System.String" Required="true" />
        <ReplacementText ParameterType="System.String" Required="true" />
      </ParameterGroup>
      <Task>
        <Reference Include="System.Core" />
        <Using Namespace="System" />
        <Using Namespace="System.IO" />
        <Using Namespace="System.Text.RegularExpressions" />
        <Code Type="Fragment" Language="cs">
          <![CDATA[
                File.WriteAllText(
                    OutputFilename,
                    Regex.Replace(File.ReadAllText(InputFilename), MatchExpression, ReplacementText)
                    );
              ]]>
        </Code>
      </Task>
    </UsingTask>
    

    然后像任何其他 MSBuild 任务一样简单地调用它

    <Target Name="AfterBuild">
      <ReplaceFileText 
        InputFilename="$(OutputPath)File.exe.config" 
        OutputFilename="$(OutputPath)File.exe.config" 
        MatchExpression="\$version\$" 
        ReplacementText="1.0.0.2" />
    </Target>
    

    以上示例将位于输出目录中的“File.exe.config”中的“$version$”替换为“1.0.0.2”。

    【讨论】:

    • 如何通过 List 作为 parameter 进行多次替换?
    • 请参阅blogs.clariusconsulting.net/kzu/… 以获得支持项目和正则表达式选项的增强版本
    • 我必须将 ToolsVersion="4.0" 添加到我的项目标签中才能使其在正确的 MSBuildToolsPath 目录中查找。
    • 对于匹配表达式,我尝试了 "[label]" 但失败了,而 "label" 工作正常。
    • 注意.NET Core自带的MSBuild版本不支持CodeTaskFactory,所以这不是一个可移植的方案。
    【解决方案2】:

    @csharptest.net 的答案很好,但在 DotNetCore 上不起作用。我会将此添加为评论,但我没有足够的声誉。

    在 DotNetCore 上你必须更新:

    • 任务工厂到“RoslynCodeTaskFactory”
    • 任务汇编到“$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll”
    • 删除对“System.Core”的引用
    • 消费目标必须将“AfterTargets”属性指定为“Build”

    其他一切都应该相同:

    <Project Sdk="Microsoft.NET.Sdk.Web">
      ...
    
      <UsingTask
        TaskName="ReplaceFileText"
        TaskFactory="RoslynCodeTaskFactory"
        AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
        <ParameterGroup>
          <InputFilename ParameterType="System.String" Required="true" />
          <OutputFilename ParameterType="System.String" Required="true" />
          <MatchExpression ParameterType="System.String" Required="true" />
          <ReplacementText ParameterType="System.String" Required="true" />
        </ParameterGroup>
        <Task>
          <Using Namespace="System"/>
          <Using Namespace="System.IO"/>
          <Using Namespace="System.Text.RegularExpressions" />
          <Code Type="Fragment" Language="cs">
            <![CDATA[  
              File.WriteAllText(
                OutputFilename,
                Regex.Replace(File.ReadAllText(InputFilename), MatchExpression, ReplacementText)
                );
            ]]>
          </Code>
        </Task>
      </UsingTask>
    
      <Target Name="AfterBuildStep" AfterTargets="Build">
        <ReplaceFileText
           InputFilename="$(OutputPath)File.exe.config" 
           OutputFilename="$(OutputPath)File.exe.config" 
           MatchExpression="\$version\$" 
           ReplacementText="1.0.0.2" />
      </Target>
    </Project>
    

    【讨论】:

      【解决方案3】:

      有一种非常简单的方法来替换文件中的字符串:

        <Target Name="Replace" AfterTargets="CoreCompile">
            <PropertyGroup>
                <InputFile>c:\input.txt</InputFile>
                <OutputFile>c:\output.txt</OutputFile>
            </PropertyGroup>
            <WriteLinesToFile
                File="$(OutputFile)"
                Lines="$([System.IO.File]::ReadAllText($(InputFile)).Replace('from','to'))"
                Overwrite="true"
                Encoding="Unicode"/>
        </Target>
      

      https://docs.microsoft.com/en-us/visualstudio/msbuild/property-functions?view=vs-2019 探索可内联的 C# 代码。 [System.Text.RegularExpressions.Regex] 已包含在列表中。

      【讨论】:

      • 小心Encoding="Unicode"!例如,app.config 的默认值为 Encoding="UTF-8",它不适用于 Unicode!
      【解决方案4】:

      编辑:这个答案已经过时了。使用下面的解决方案...

      使用 ReadLinesFromFile 任务从 xy.xml 文件中获取替换字符串。 Check this

      然后使用 xy.xml 中的值作为 FileUpdate 任务的替换字符串。 Check this

      把它们放在一起;)

      【讨论】:

      • 我必须从文件中读取具有属性名称对象的特定节点并将该节点替换为另一个节点?如何处理这个问题?
      • 这些任务来自 MSBuildCommunityTasks 项目,对于像这样的简单任务,安装它可能是非常不受欢迎的。
      【解决方案5】:

      您可以使用 MSBuild 社区任务中的任务 FileUpdate,如文章中所述 http://geekswithblogs.net/mnf/archive/2009/07/03/msbuild-task-to-replace-content-in-text-files.aspx

      【讨论】:

        【解决方案6】:

        如果您不想使用第三方(社区)二进制文件,也不想将代码嵌入到您的 msbuild 项目中,我建议您创建一个简单的任务库来实现 File.WriteAllText 并且以后可以托管其他任务:

        using System.IO;
        using Microsoft.Build.Framework;
        using Microsoft.Build.Utilities;
        
        public class FileWriteAllText : Task
        {
            [Required]
            public string Path { get; set; }
            [Required]
            public string Contents { get; set; }
        
            public override bool Execute()
            {
                File.WriteAllText(Path, Contents);
                return true;
            }
        }
        

        然后就可以在msbuild中替换、追加等:

        <UsingTask TaskName="FileWriteAllText" AssemblyFile="MyTasks.dll" />
        <FileWriteAllText Path="test.xml"
             Contents="$([System.Text.RegularExpressions.Regex]::Replace(
                 $([System.IO.File]::ReadAllText('test.xml')), 'how r u', 'i am fine'))" />
        

        【讨论】:

          【解决方案7】:

          我针对位于 Unix 驱动器上的同一个文件运行了这两个替换,并使用了它的 unc 路径 \server\path...:

          <ReplaceFileText
            InputFilename="$(fileToUpdate)"
            OutputFilename="$(fileToUpdate)"
            MatchExpression="15.0.0"
            ReplacementText="15.3.1"/>
          
          
          <FileUpdate Files="$(fileToUpdate2)"
                      Regex="15.0.0"
                      ReplacementText="15.3.1" />
          

          并且上面的cs自定义动作没有添加bom;但是 FileUpdate 做到了:

          %head -2 branding.h branding2.h
          ==> branding.h <==
          #/* branding.h
          #** This file captures common branding strings in a format usable by both sed and C-preprocessor.
          
          ==> branding2.h <==
          #/* branding.h
          #** This file captures common branding strings in a format usable by both sed and C-preprocessor.
          

          感谢 csharptest.net - 我正在为 unix 构建使用 perl 替代命令执行 exec。

          【讨论】:

          • 您可以将编码参数传递给 File.WriteAllText 以添加 BOM,但无论哪种方式都可以完成 :)
          【解决方案8】:

          James 的更新回答

            <UsingTask TaskName="ReplaceTextInFiles" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.$(VsBuildTaskBinarySuffix).dll">
          <ParameterGroup>
            <MatchExpression ParameterType="System.String" Required="true" />
            <ReplacementExpression ParameterType="System.String" Required="true" />
            <InputFile ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" />      
            <IsTextReplaced ParameterType="System.Boolean"  Output="True"/>
          </ParameterGroup>
          <Task>
            <Reference Include="System.Core" />
            <Using Namespace="System" />
            <Using Namespace="System.IO" />
            <Using Namespace="System.Text.RegularExpressions" />
            <Code Type="Fragment" Language="cs">
              <![CDATA[
                bool isMatchFound = false;
                string filecontent = "";
                string path = InputFile.ItemSpec;
          
                Log.LogMessage(MessageImportance.High, "[ReplaceTextInFiles]: Match= " + MatchExpression);
                Log.LogMessage(MessageImportance.High, "[ReplaceTextInFiles]: Replace= " + ReplacementExpression);
          
                IsTextReplaced = false;
                using(StreamReader rdr = new StreamReader(path))
                {
                  filecontent = rdr.ReadToEnd();
                  if (Regex.Match(filecontent, MatchExpression).Success)
                  {
                    filecontent = Regex.Replace(filecontent, MatchExpression, ReplacementExpression);
                    isMatchFound = true;            
                  }
                }
          
                if(isMatchFound){
                  using(StreamWriter wrtr = new StreamWriter(path))
                  {
                    wrtr.Write(filecontent);
                    IsTextReplaced = true;
                    Log.LogMessage(MessageImportance.Normal, "[ReplaceTextInFiles]: Replaced text in file:" + path);
                  }
                }       
              ]]>
            </Code>
          </Task>
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-02-08
            • 2016-08-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多