Adding a line to the start of a file, based on other lines in the file

Posted on

Problem :

I have files of the following structure:

foo.bar.baz () ->
  templateUrl: require('x.jade')

I need to transform these files such that:

  1. They are prefixed with the line var templateUrl = require('x.jade')
  2. The templateUrl: line is replaced with templateUrl: templateUrl.

How can I do this? One option is a bash script that, for each file:

  1. greps for templateUrl: to get the require part.
  2. cats the var templateUrl line and the file together into a new file, and then replaces the original file.
  3. Uses sed to replace templateUrl: require... with templateUrl.

But can this be done with a single sed or awk script that can be executed for each file?

Solution :

@simlev’s perl solution really has less steps, but I’d like to present my sed command. It is more relevant because the OP already envisioned using sed and explicitly asks for a sed or awk script.

sed -n '1h;:a;n;s/require/&/;tInsert;H;ba;:Insert;H;s/^/var/;s/:/ =/;G;s/: .*/: templateUrl/;p'

Note: This command will not work if the file contains more than one line with : or require.

Explanation:

sed -n        # -n option disables automatic printing.

1 h           # h command puts the first line (address 1) in the hold space,
              # replacing any previous content.

:a            # Set a mark with label 'a'.

n             # n command prints the pattern space and loads next line of input,
              # replacing all pattern space content.
              # But it will not print because of -n option.

s/require/&/  # Test if the pattern space has the line OP wants edit.

t Insert      # If substitution was made, t command jumps to the mark with label 'Insert'.

H             # If not, continue with H command, it appends a 'n' to the hold space content
              # and then appends the pattern space to the hold space.

b a           # b (branch) command, jumps to the mark with label 'a'.

:Insert       # Exit from the 'a' loop. Here the hold space has all the lines that precede
              # the line with 'replace', and pattern space has the line with 'replace'.
H             # The line with 'replace' is appended to the hold space too.

s/^/var/      # Here sed finally edits the line, replacing the beginning with 'var' ...

s/:/ =/       # and only the first ':' with ' ='.

G             # G command appends the content of hold space to the edited pattern space,
              # here that line edited above in pattern space becomes the first line.

s/: .*/: templateUrl/    # One more substitution.

p'            # Finally, print the pattern space.

Input file(s):

foo.bar.baz () ->
  templateUrl: require('x.jade')

perl command:

perl -i -0777pe '$_=~s/templateUrl:( K.*)/templateUrl/;print"var templateUrl =$1n"' *

Output file(s):

var templateUrl = require('x.jade')
foo.bar.baz () ->
  templateUrl: templateUrl

Breakdown:

  • perl scripting language that excels at text manipulation
  • -i edit files in-place
  • -0777 work with the file as a whole, as opposed to line-by-line
  • p print the file (in this case the file will be saved, due to the -i switch)
  • e execute the following command, as opposed to executing code that is saved in a file
  • ' start of instructions
  • $_=~s perform a substitution on the entire file ($_)
  • /templateUrl:( K.*))/ look for a line matching the regex templateUrl: .*, and capture the string matching the parenthesised subexpression  .* to a variable (called $1 by default)
  • templateUrl/ replace the part after the K marker in the matched line with text templateUrl
  • ; separates between instructions
  • print"var templateUrl =$1n" print var templateUrl =, the contents of $1 and a newline
  • at this point, the rest of the file is implicitly printed, because the p switch was specified
  • ' end of instructions
  • * process every file in the current directory

There can be of course different approaches, such as:

perl -i -ne 'if ($_=~s/templateUrl:( K.*)/templateUrl/){$a="templateUrl =$1"} {$file.=$_} END{print"var $an$file"}' *

AWK:
Since the question is about sed or awk, it is worth noting that either approach can be just as easily implemented in awk:

awk -i 'BEGIN {RS=""} {match($0,/templateUrl:( .*)/,m); gsub("templateUrl: .*","templateUrl: templateUrl"); print "var templateUrl ="m[1]$0}' *
awk -i '/templateUrl: / {a="templateUrl = "$2;gsub("templateUrl: .*","templateUrl: templateUrl")} NR==1 {file=$0} NR==2{file=file"n"$0} END{print a"n"file}' *

Leave a Reply

Your email address will not be published. Required fields are marked *