1

In a text like

.....
.....
hello
door
.....
.....
box
wall
floor
.....

I want to move the block "box\n wall\n floor" to below the line that matches "hello" (which occurs at a line before the block). The result should be this:

.....
.....
hello
box
wall
floor
door
.....
.....

How can I do this with sed or awk? I did the following in gawk, but thought there might be a better way to do this in either [ga]wk or sed:

gawk -v k=1 '/^box/{flg=1;j=1}
    flg{tmp1[j++]=$0}!flg{tmp2[k++]=$0}
    /^floor/{flg=0}
    END{for (j=1;j<=length(tmp2);j++) {
           print tmp2[j]
           if (tmp2[j]~/hello/) for (i=1;i<=length(tmp1);i++) print tmp1[i]}}'
secluded
  • 485
  • 4
  • 12

3 Answers3

3

While it's possible to use 'sed' and 'awk' for simple editing tasks, it require scripting logic that is sometimes complex to code and debug - mostly because those tools handle the input one line at a time ("stream" editing).

Consider instead using the 'ed' command line editor, which has most of 'sed' editing capabilities (and more), without the limit of having to deal with the input file one line at a time.

ed file <<__END__
/^box$/,/^floor$/m/hello/
wq
__END__

The script moves the block defined with box to floor to the line after the hello line, then write the file.

As a side note - the main challenge for 'sed' and 'awk' in this task is the need to move the text block 'backward'. Moving text block 'forward' is relatively easy with those tools, as it is possible to use side storage (hold buffer for sed, variables for awk) to hold the block, while searching for the place to place it.

dash-o
  • 13,723
  • 1
  • 10
  • 37
  • Nice solution! Thanks. Do you know if it's possible to move a block to "before" a line? (/hello/ in the example) – secluded Jan 05 '20 at 19:59
  • I believe possible to use ‘...m/hello/-1’ need to check - I am away from desktop – dash-o Jan 05 '20 at 20:01
  • Could you please let me know what the -1 at the end means? – secluded Jan 05 '20 at 20:04
  • Found the following from documentation. "...m/hello/-" would do it too :) Thanks again. Addresses can be followed by one or more address offsets, optionally separated by whitespace. Offsets are constructed as follows: '+' or '-' followed by a number adds or subtracts the indicated number of lines to or from the address. '+' or '-' not followed by a number adds or subtracts 1 to or from the address. A number adds the indicated number of lines to the address.' – secluded Jan 05 '20 at 20:14
  • If you like this (or other solution) consider accepting them. – dash-o Jan 05 '20 at 20:15
0

You can do what you are attempting with a couple of flags. One to control printing f and one to control whether to save the next line as a, n. That would allow:

awk -v f=0 '/^box/{f=0}; f==0; n==1{a=$0;n=0}; /^hello/{f=1;n=1} /^floor/{print a}' file

Where the first rule just checks whether the line starts with box and if so, sets print flag f=0 (back to default). If f==0 the default print rule is applied. If n==1 then capture the next line as a and set the capture flag n back to 0. If the line is hello, then set print flag f=1 to stop printing, set n=1 to capture next line. Finally if line is floor, just output the record saved in a.

Example Use/Output

With your input in file

$ awk -v f=0 '/^box/{f=0}; f==0; n==1{a=$0;n=0}; /^hello/{f=1;n=1} /^floor/{print a}' file
.....
.....
hello
box
wall
floor
door
.....

There are probably simpler ways to do this. This just came to mind first.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

Following David C. Rankin solution, but more precise.

Here is an awk solution that does not overwrite, but moves a section.

script.awk

svSctn!=1;
/^hello/ {svSctn++;next}
/^box/,/^floor/ {
    print;
    if ($0 ~ /^floor/) {printf("%s", mvdStr);svSctn++;}
    next;
}
svSctn == 1 {mvdStr = mvdStr $0 ORS}

input.txt

...l1
...l2
hello
door
....l3
....l4
box
wall
floor
....l5

runing:

awk -f script.awk input.txt

output:

...l1
...l2
hello
box
wall
floor
door
....l3
....l4
....l5

explanation:

savedSection != 1 { # for every line, not in saved section (Initially savedSection=0)
    print; # print the current line
}
/^hello/ { # when foudn the line containing "Hello" (already printed)
    savedSection++;  # Mark this line and following are in savedSection=1
    next; # skip processing to read next line
}
/^box/,/^floor/ { # when in ranage section between and including "box" to "floor"
    print; # print the current line
    if ($0 ~ /^floor/) { # if current line "floor" (already printed)
        printf("%s", movedSection); # insert here the skipped and saved section
        savedSection++; # Mark this is in savedSection=2, so print the rest of line.
    }
    next; # skip processing to read next line (do not add this range into savedSection)
}
savedSection == 1 { # this is saved section.
     movedSection = movedSection $0 ORS; # do not print, just append line to movedSection variable
}
Dudi Boy
  • 4,551
  • 1
  • 15
  • 30