I have one file like:

ID_SOUR_CALENDAR BIGINT NOT NULL DEFAULT 0  COMPRESS 0 ,
UNIQUE PRIMARY INDEX ( CALENDAR_DATE );
ID ,
ID_SOUR ,
PRIMARY INDEX ( CALENDAR_DATE );

I want to replace the ',' by ')' in the line just before the line containing PRIMARY.

The result should be:

ID_SOUR_CALENDAR BIGINT NOT NULL DEFAULT 0  COMPRESS 0 )
UNIQUE PRIMARY INDEX ( CALENDAR_DATE );
ID ,
ID_SOUR )
PRIMARY INDEX ( CALENDAR_DATE );
up vote 9 down vote accepted

Using GNU sed:

sed  'N;s/,\(\s*\n.*PRIMARY\)/)\1/;P;D' file

ID_SOUR_CALENDAR BIGINT NOT NULL DEFAULT 0  COMPRESS 0 )
UNIQUE PRIMARY INDEX ( CALENDAR_DATE );
ID ,
ID_SOUR )
PRIMARY INDEX ( CALENDAR_DATE );
  • N Read/append the next line of input into the pattern space.
  • P Print up to the first embedded newline of the current pattern space.
  • D Delete up to the first embedded newline in the pattern space. Start next cycle, but skip reading from the input if there is still data in the pattern space.

Try reversing the file, doing the string replacement on the line after PRIMARY, then re-reversing the file:

tac file | sed '/PRIMARY/ {n; s/,$/)/}' | tac
  • 2
    +1 for the smartest way. – msp9011 Sep 4 at 15:14
  • 2
    @SivaPrasath - not really, this will fail if the input contains consecutive lines matching PRIMARY – don_crissti Sep 4 at 15:29
  • @don_crissti ur right!!! at least it works for the OP's input. – msp9011 Sep 4 at 15:44

try this,

perl -0777 -pe 's/,([^\n,]*\n[^\n]*PRIMARY)/)$1/g'

This will replace the last , with ) in lines before any line including PRIMARY.

Explanation

  • perl -0777 slurp whole file (read as one line)
  • s/search_pattern/replacement/g Substitute search pattern with replacement globally
  • 1
    Note that . meta char won't cross line boundaries, unless the /s modifier is in effect. Hence, you can change the regex to s/,(?=[^,]*\n.*PRIMARY)/)/g – Rakesh Sharma Sep 4 at 17:44
  • 1
    ? I don't use . in my regex. – RoVo Sep 5 at 8:32

If you use ed rather than sed you can use a regex address with negative offset:

g/PRIMARY/-1 s/,$/)/

Ex.

$ printf 'g/PRIMARY/-1 s/,$/)/\n,p\nq\n' | ed -s file
ID_SOUR_CALENDAR BIGINT NOT NULL DEFAULT 0  COMPRESS 0 )
UNIQUE PRIMARY INDEX ( CALENDAR_DATE );
ID ,
ID_SOUR )
PRIMARY INDEX ( CALENDAR_DATE );

or (for in-place editing)

printf 'g/PRIMARY/-1 s/,$/)/\nwq\n' | ed -s file

The replacement will match ALL instances of /PRIMARY/ - if you only want to replace the first, remove the g modifier.

  • +1 for use of The Standard Text Editor – mirabilos Sep 4 at 16:54

With POSIX-ly sed you can do the following:

sed -e '
    /PRIMARY/!{x;1!p;d;}
    x;s/,/)/;$G
' input-file.txt

Output:

ID_SOUR_CALENDAR BIGINT NOT NULL DEFAULT 0  COMPRESS 0 )
UNIQUE PRIMARY INDEX ( CALENDAR_DATE );
ID ,
ID_SOUR )
PRIMARY INDEX ( CALENDAR_DATE );

Working:

  • For non-PRIMARY lines, we save the line and print the one we saved in the hold.
  • And for the PRIMARY, we retrieve the previous and effect the transformation.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.