Search code examples
sed

How to replace mutiline pattern space in sed


I have a data which is shown like below in a file test.cs

using System;
using System.Data;
using Sample.test.value;
namespace Sample.test {

  class testclass{  

    public testclass(){


    }
  }
}

I want the output to be

namespace Sample.test {
 using System;
 using System.Data;
 using value;
  class testclass{  

    public testclass(){


    }
  }
}

This i want to achieve only by using sed

I tried below sed script and able to copy and paste the using inside namespace

/^using/{
 H
 d
}
/^namespace/{
 G
}

But unable to replace the namespace in using statement. Here in this example I took an example as "Sample.test" namespace. But it real case it can be anything.


Solution

  • This sed script transforms your test data but will need alteration if your files contain more than one using/namespace block, or if the whitespace is not identical.

    # match using lines
    /^using/{
        s/^/ /  # prepend whitespace 
        H       # append to hold
        d       # don't print (yet)
    }
    
    # match namespace lines
    /^namespace/{
        G       # append the using block after namespace line
    
        # loop while we can strip a namespace name from a using line
        :a
        s/\(\([^ ]*\) {.*using \)\2\./\1/
        ta
    
        # read in next line and delete it if empty
        N
        s/\n\n/\n/g
    }
    
    # implicit print
    

    The second s/// command looks for the name that precedes a { (ie. \([^ ]*\)) followed by a using line with that same name followed by a period (ie. \2\.). The entirety of the match aside from the trailing \2\. is available as \1 because of the initial set of \(...\) and replaces the original text.

    Note: Using \n in the replacement is not portable. If using a version of sed that does not understand it, use an escaped literal newline:

        # ...
    
        N
        s/\n\n/\
    /g
    }
    

    Building on @Jotne's answer, an equivalent awk version is:

    /^using/ {
        # store using lines, along with a prepended space
        a[++i] = " " $0
        next
    }
    /^namespace/ {
        print
    
        # strip namespace name from using lines, then print
        r = " " $2 "[.]"
        for(i in a) {
            sub(r, " ", a[i])
            print a[i]
        }
    
        # delete subsequent line if it is blank
        getline
        if ($0) print
    
        next
    }
    
    # print every other line
    1
    

    The regex here (r) looks for a space followed by the namespace name (it should be $2 on the namespace line) followed by a period. The sub() replaces any occurrences of this regex in the saved using lines with just a single space.