0

Giving an example to explain the problem. I have a file with

AAA

BBB

CCC

DDD

EEE

FFF

ABAB

ACAC

GGG

HHH

I want to shift 2 lines after match ABAB to just before DDD. So, the modified file would look like:

AAA

BBB

CCC

ABAB

ACAC

DDD

EEE

FFF

GGG

HHH

Looking for some cool way to handle this preferably using sed.

  • Are there really blank lines between each entry? – steeldriver Feb 24 at 3:32
2

NOTE: I'm assuming your data does not have blank lines between each entry; if it does, then you will need to address four lines i.e. change + to +3

With GNU ed:

$ ed -s file <<EOF
/ABAB/,//+m?DDD?-
,p
q
EOF

where

  • /ABAB/,//+ addresses a range of lines from /ABAB/ to the previous match // plus one line
  • m moves the addressed lines to
  • ?DDD?- the previous line matching DDD, minus one line
  • ,p print the whole buffer

As a one-liner,

printf '/ABAB/,//+m?DDD?-\n,p\nq\n' | ed -s file

To edit file in place, replace ,p\nq\n by ,wq\n (write and quit).

  • Works great! How do we handle this when we have to do this processing on output of a previously processed output? sat if I want to cat file and then do this processing after that, how do I do it? – Pratap Feb 24 at 7:10
  • @Pratap yes you can - but it's not just a matter of piping the previous command output, since ed reads its commands via standard input. Instead, you have to pass the command as a special "file", preceding it with a ! character e.g. printf '/ABAB/,//+m?DDD?-\n,p\nq\n' | ed -s \!'cat file'. – steeldriver Feb 24 at 14:20
1

You wanted with sed so you can do it as shown:

sed -e '
    /DDD/,/ABAB/! b
    H;/ABAB/!{$!d;};g
    s/\(\n.*\)\n\(.*\)/\2\1/
' input.txt

This gets straightforward with the ed editor:

 ed -s input.file -  <<\eof
 /ABAB/m?DDD?-
 wq
 eof
0

Python (single read)

Reading a file in one and storing what's between the two patterns can be done as so:

#!/usr/bin/env python3
import sys

flag=False
vals = []
with open(sys.argv[1]) as fd:
    for line in fd: 
        if line.strip() == "DDD" or flag:
            # encountered line where we should stop appending values
            if line.strip() == "ABAB": 
                flag = False
                # print the line and two others, then move in what was between originally
                print(line.strip())
                for i in range(2):
                    print(fd.readline().strip())
                print("\n".join(vals))
                continue
            # store values while we haven't hit ABAB
            flag = True
            vals.append(line.strip())
            continue

        print(line.strip())

Python ( double read)

Re-using original awk idea I had to read the file twice, we can do the same in Python:

#!/usr/bin/env python3
import sys


flag_pos,match = 0,0
vals = []
with open(sys.argv[1]) as fd:
     for index, line in enumerate(fd):
         if line.strip() == "DDD":
             flag_pos = index
         if line.strip() == "ABAB":
             vals.append(line.strip())
             fd.readline()
             vals.append(fd.readline().strip())


with open(sys.argv[1]) as fd:
    for index,line in enumerate(fd):
        if index == flag_pos:
            print("\n\n".join(vals),"\n")
        if line.strip() in vals:
            fd.readline()
            continue
        print(line.strip()) 

This script can be saved as movelines.py and called as ./movelines.py input.txt

AWK

This is doable in gawk probably much more easily than sed:

$ awk 'NR==FNR && $0=="ABAB" { a[i++]=$0;getline;getline; a[i++]=$0; }; NR!=FNR { if($0=="DDD") for(val in a) printf "%s\n\n",a[val];  if($0 == "ABAB") {getline;getline;getline;} print $0   }' input.txt input.txt
AAA

BBB

CCC

ABAB

ACAC

DDD

EEE

FFF


GGG

HHH

The trick here is that we pass the file to awk twice for reading, and distinguish between first reading that finds the lines we want to move and second reading where we actually move them.

If your actual file does not have blank lines as in the example you provided, you just need one getline instead of two and "%s\n" in the second part of the code will suffice.

For readability, here's a multipline version of the code with comments:

# on first reading NR != FNR, 
# so lets store ABAB and the other line into array
awk 'NR==FNR && $0=="ABAB" { 
        # i variable will be initialized once incremented first time
        a[i++]=$0;getline;getline; a[i++]=$0; 
    };
    # Here we are reading the same file second time 
    NR!=FNR { 

        if($0=="DDD") 
            for(val in a) 
                printf "%s\n\n",a[val]; 
        # Skip what we matched already
        if ($0 == "ABAB"){
            getline;
            getline;
            getline;
        }
        print $0   
     }' input.txt input.txt
0

I have done by below method

command

Step1: h=`sed -n '/[A-Za-z]\{4\}/p' filename| sed -n '1p'`
step2:m=`sed -n '/[A-Za-z]\{4\}/p' filename| sed -n '2p'`
step3

    sed '/[A-Z]\{4\}/d' filename|sed "/CCC/s/.*/&\n\n$h\n\n$m/g"| sed '/^$/d' 

output

AAA
BBB
CCC
ABAB
ACAC
DDD
EEE
FFF
GGG
HHH

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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