Luigi
Luigi

Reputation: 388

Replace value across multiple lines in bash

I'm struggling even with all the examples available on stackoverflow and google.

Basically i have the following text

    /start r.start ""
      GAIN 0x256 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x256 0x2 10 0xA3
      /end MACRO
      OTHER
    /end

and i need to read through this file searching for a name e.g. r.start which is passed as $1 and substitute e.g. 0x256 with a value that i pass as $2. There a two instance to substitute, line 2 and line 7.

Things i know:

  1. /start is preceded by 4 spaces
  2. GAIN is preceded by 6 spaces
  3. \r\n or \n might be present
  4. CODE is preceded by 8 spaces

Till now i've reached this point

pattern="N;s\s\/start r.start \"\"\n      GAIN 0x\(.*\)"
replacement"\/start r.start \"\"\n      GAIN 0x82"
sed -e "$pattern/$replacement/p" test.txt

but i get nothing. I was also able to substitute the first line but for whatever reason it pasted me the first two lines twice on top of each other

the expected value assuming the following call

./run.sh r.start 0x284569

should be

    /start r.start ""
      GAIN 0x284569 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x284569 0x2 10 0xA3
      /end MACRO
      OTHER
    /end

Here's my best shot for the second part

var="r.start"
val="0x48209F82"

pattern="\(CODE \"$var\" \).*\(.*\)"
replacement="\1$val\2"
sed "s/$pattern/$replacement/g" test.txt

The problem is that it is deleting everything after the value substitution. I can't put in \2 the following chars

EDIT:

By doing the following

var="r.start"
val="0x48209F82"

pattern="\(CODE \"$var\" \).*\(\s.*\s.*\s.*\)"
replacement="\1$val\2"
sed "s/$pattern/$replacement/g" test.txt

I get what i want but it feels a little bit dirty, how do i reduce the last part in case of a variable number of char? Can i just somehow match everything till end of line?

Upvotes: 0

Views: 159

Answers (4)

Jon
Jon

Reputation: 3671

How about something like the following? It uses a sed range to select everything between the /start and /end, and within the range uses a block to substitute the value for lines which match GAIN and CODE.

$ cat foo.txt
    /start r.start ""
      GAIN 0x256 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x256 0x2 10 0xA3
      /end MACRO
      OTHER
    /end
$ sed '\#/start r\.start#,\#/end#{/\(GAIN\|CODE "r\.start"\)/s/0x[a-fA-F0-9]\+/0x284569/}' foo.txt
    /start r.start ""
      GAIN 0x284569 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x284569 0x2 10 0xA3
      /end MACRO
      OTHER
    /end

The sed script looks like this when formatted nicely:

\#/start r\.start#,\#/end# {
    /\(GAIN\|CODE "r\.start"\)/s/0x[a-fA-F0-9]\+/0x284569/
}

Note

  • the use of \#...# instead of /.../ for the range patterns, so we don't have to quote slashes
  • the use of /patten1/s/pattern2/string/ to replace pattern2 only for lines containing pattern1
  • the use of a {...} block so that the substitution only applies between /start and /end

You can parameterise the pattern and value like so:

#!/bin/bash

var='r\.start'
val='0x48209F82'

sed '\#/start '$var'#,\#/end#{/\(GAIN\|CODE "'$var'"\)/s/0x[0-9a-fA-F]\+/'$val'/}' foo.txt

Upvotes: 2

Toto
Toto

Reputation: 91518

Here is a perl way:

#!/usr/bin/perl 
use strict;
use warnings;

# retrieve the 2 parameters
my $start = shift @ARGV or die "missing 1rst arg";
my $repl =  shift @ARGV or die "missing 2nd arg";

# input file
open my $fh_in, '<', 'file.txt' or die "$!";
# output file
open my $fh_out, '>', 'output' or die "$!";

# loop through input file
while(<$fh_in>) {
    # if we are between /start {1srt parameter} and /end
    if (/^ {4}\/start\h+$start/ ... /^ {4}\/end\h*$/) {
        # substitute 0x.... by {2nd parameter}
        s/^(?: {6}GAIN | {8}CODE "$start" )\K0x\w+/$repl/;
    }
    print $fh_out $_;
}

In action:

cat file.txt 
    /start other.start ""
      GAIN 0x256 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x256 0x2 10 0xA3
      /end MACRO
      OTHER
    /end
    /start r.start ""
      GAIN 0x256 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x256 0x2 10 0xA3
      /end MACRO
      OTHER
    /end

./test.pl r.start 0x123456

cat output 
    /start other.start ""
      GAIN 0x256 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x256 0x2 10 0xA3
      /end MACRO
      OTHER
    /end
    /start r.start ""
      GAIN 0x123456 __POSITIVE 1 FOO
      OTHER
      OTHER
      /start MACRO
        200
        CODE "r.start" 0x123456 0x2 10 0xA3
      /end MACRO
      OTHER
    /end

Upvotes: 0

potong
potong

Reputation: 58578

This might work for you (GNU sed):

sed -E '/^    \/start r\.start /{:a;N;/^    \/end$/M!ba;s/^(      GAIN |        CODE "r\.start" )0x\S+/\10x284569/Mg}' file

Gather up lines between /start and /end and using pattern matching replace the desired values.

The solution may be placed in a function:

f () { sed -E '/^    \/start '"$1"' /{:a;N;/^    \/end$/M!ba;s/^(      GAIN |        CODE "'"$1"'" )0x\S+/\1'"$2"'/Mg}' "$3"; }

And called:

f r\\.start 0x284569 file

Upvotes: 0

Toby Batch
Toby Batch

Reputation: 1

You want sed for this, and don't need a script:

Assuming the text is a file called the_text:

sed "s/0x256/REPLACETEXT/g" the_text

If you ant to do that with out echoing out the text then add a -i

sed -i "s/0x256/REPLACETEXT/g" the_text

It is possible to daisy chain these commands or embed them into single script, this one echoes to the standard out:

#!/bin/bash

sed "s/0x256/${2}/g" $1
sed "s/0x256/${2}/g" $1

Upvotes: -1

Related Questions