0

I have a file foo I want to edit. I want to substitute all the lines of the file before a certain pattern match $node_(2)

$node_(0) set X_ 46.188445620285734
$node_(0) set Y_ 400.0
# $ns_ at 0.0 "$node_(0) setdest 46.188445620285734 400.0 0.0"
$ns_ at 4.230260628522046 "$node_(0) setdest 140.0 400.0 11.073898626729083"
$ns_ at 12.70167232749509 "$node_(0) setdest 140.0 293.81155437971427 11.073898626728642"
# $ns_ at 22.290747430998636 "$node_(0) setdest 140.0 293.81155437971427 0.0"
$ns_ at 24.512130351924498 "$node_(0) setdest 140.0 121.22627768528247 11.143254728766196"
$node_(1) set X_ 284.3754249089888
$node_(1) set Y_ 400.0
$ns_ at 0.0 "$node_(1) setdest 358.5058741786957 400.0 10.938908248230844"
# $ns_ at 6.776768539190925 "$node_(1) setdest 358.5058741786957 400.0 0.0"
$ns_ at 8.52331532068547 "$node_(1) setdest 400.0 400.0 11.27995015881709"
$ns_ at 12.201888828288247 "$node_(1) setdest 400.0 341.4941258213043 11.279950158817115"
# $ns_ at 17.388602719303435 "$node_(1) setdest 400.0 341.4941258213043 0.0"
$ns_ at 22.22922653365822 "$node_(1) setdest 400.0 141.4941258213043 11.258336966410015"
$node_(2) set X_ 270.0 //so basically here is the pattern match
$node_(2) set Y_ 293.0222761518543
.
.
.

The file should then look like this

$node_(0) set X_ 10.0
$node_(0) set Y_ 10.0
$node_(1) set X_ 510.0
$node_(1) set Y_ 510.0
$node_(2) set X_ 270.0 //pattern and everything after it is left intact
$node_(2) set Y_ 293.0222761518543
.
.
.

I tried usind sed

sed -i -e '/$node_(0)/,/$node_(2)/c\$node_(0)\ set X_ 10.0 \n$node_(0) set Y_ 10.0\n$node_(1) set X_ 510.0\n$node_(1) set Y_ 510.0' foo

but it removes the first $node_(2) line completely. Also, the number of lines before $node_(2) is changing for every new foo file so I can't count lines to substitute every time.

pengu1n
  • 31
  • 3
  • `$` in a regex matches end of line, you want to use `\$` or `[$]` to match a literal dollar sign. – tripleee Dec 12 '17 at 15:32
  • 1
    Oh, I think I understand now. You want to delete all lines before the first one matching your pattern and replace it with hardcoded lines. I recommend you use the sed [`r` command](https://www.gnu.org/software/sed/manual/sed.html#index-r-_0028read-file_0029-command) ("read file"). – Benjamin W. Dec 12 '17 at 15:36
  • @BenjaminW. exacltly! I already know what I'm substituting them with. I'll have a look at the link you posted and see if I can make it work – pengu1n Dec 12 '17 at 15:44
  • @tripleee thanks for pointing that out. I'll modify the command – pengu1n Dec 12 '17 at 15:48

4 Answers4

2

If I put the lines you want to insert into a separate file replace:

$node_(0) set X_ 10.0
$node_(0) set Y_ 10.0
$node_(1) set X_ 510.0
$node_(1) set Y_ 510.0

I can do this:

$ sed -n '/\$node_(2)/,$p' infile | cat replace -
$node_(0) set X_ 10.0
$node_(0) set Y_ 10.0
$node_(1) set X_ 510.0
$node_(1) set Y_ 510.0
$node_(2) set X_ 270.0 //so basically here is the pattern match
$node_(2) set Y_ 293.0222761518543

The sed command suppresses printing by default (-n), then prints all lines from the first match of \$node_(2) (escaping $ not strictly necessary here, but always a good idea if you don't use it as end-of-line anchor) to the end of the file ($ address).

The cat command then first prints your replacement file, then the output of the sed command.

Or with the sed r ("read") command (hat tip to tripleee for suggesting the use of -e):

$ sed -n -e '1r replace' -e '/\$node_(2)/,$p' infile
$node_(0) set X_ 10.0
$node_(0) set Y_ 10.0
$node_(1) set X_ 510.0
$node_(1) set Y_ 510.0
$node_(2) set X_ 270.0 //so basically here is the pattern match
$node_(2) set Y_ 293.0222761518543

The commands are separated into multiple -e chunks as r is a bit picky about what it sees as the filename to read. It would break if $node_(2) appears on the very first line, but if that were the case, you could really just concatenate your files anyway.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
  • This looks like you wanted to use `sed r` (like you mentioned in a comment) and then gave up. Like the `i` command, `r` is tricky, but try using multiple `-e` options, like `sed -n -e 1r\\ -e replace.txt -e '/\$node_(2)/$p' infile` (not sure about the backslashes after `r`?) – tripleee Dec 12 '17 at 20:18
  • @tripleee I thought about using `r`, but wasn't immediately clear about reading just once, and I don't think you can actually do it with a non-GNU sed, see https://stackoverflow.com/questions/9970124/sed-to-insert-on-first-match-only - and looking above, I see that SLePort has suggested a solution like that. – Benjamin W. Dec 12 '17 at 20:34
1

Just invert the logic.

sed -n '1i \
$node_(0) set X_ 10.0\
$node_(0) set Y_ 10.0\
$node_(1) set X_ 510.0\
$node_(1) set Y_ 510.0
    /[$]node_(2)/,$p' file

The 1 is an address expression which selects the first line and i is a command to insert text. Alas, the i command is not completely standardized, so implementation details and syntax (especially for inserting multiple lines) differs between platforms. For legibility and portability, maybe switch to Perl:

perl -i -ne 'BEGIN { print <<__the_end_of_the_BEGIN__; }
\$node_(0) set X_ 10.0
\$node_(0) set Y_ 10.0
\$node_(1) set X_ 510.0
\$node_(1) set Y_ 510.0
__the_end_of_the_BEGIN__
    $p=1 if /^\$node_\(2\)/;
    print if $p;' file

You could write a very similar Awk script but that doesn't portably support in-place editing of the file like sed -i/perl -i.

Perl uses dollar signs as a sigil for scalar variables, so those have to be escaped to be printed literally, and the regular expression dialect is different (and vastly more powerful) than traditional sed or Awk, but the only difference here is that parentheses need to be backslashed or otherwise escaped.

tripleee
  • 175,061
  • 34
  • 275
  • 318
1

With GNU sed:

sed '0,/$node_(2)/{//!d;s//text to add\n&/}' file
SLePort
  • 15,211
  • 3
  • 34
  • 44
-1

I recommend a multi-step process. First, have the lines you want already in a file, such as header.txt.

Now, find line number of the last line you want to delete:

$ first=$( grep -n '^\$node_(2)' test.txt | head -1 | cut -d: -f1 )
$ last=$(( first -1 ))

Delete those lines from the file:

$ sed "1,${first}d" myfile.txt

Now, cat the two files into a temp file and rename the temp file:

$ cat header.txt myfile.txt > tmp$$  &&  mv tmp$$ myfile.txt
Jack
  • 5,801
  • 1
  • 15
  • 20
  • Using a regex to find the line number of a line so you can remove it in a different command is decidedly an anti-pattern. This is inefficient, especially if the files are big, but in any case, `sed` can delete lines by regex just fine. – tripleee Dec 12 '17 at 16:06