Alsaraha
Alsaraha

Reputation: 89

Find and replace and move a line that contains a specific string

Assuming I have the following text file:

a b c d 1 2 3
e f g h 1 2 3
i j k l 1 2 3
m n o p 1 2 3

How do I replace '1 2 3' with '4 5 6' in the line that contains the letter (e) and move it after the line that contains the letter (k)?

N.B. the line that contains the letter (k) may come in any location in the file, the lines are not assumed to be in any order

My approach is

  1. Remove the line I want to replace
  2. Find the lines before the line I want to move it after
  3. Find the lines after the line I want to move it after
  4. append the output to a file
grep -v 'e' $original > $file
grep -B999 'k' $file > $output
grep 'e' $original | sed 's/1 2 3/4 5 6/' >> $output
grep -A999 'k' $file | tail -n+2 >> $output
rm $file
mv $output $original

but there is a lot of issues in this solution:

  1. a lot of grep commands that seems unnecessary
  2. the argument -A999 and -B999 are assuming the file would not contain lines more than 999, it would be better to have another way to get lines before and after the matched line

I am looking for a more efficient way to achieve that

Upvotes: 2

Views: 109

Answers (4)

potong
potong

Reputation: 58488

This might work for you (GNU sed):

sed -n '/e/{s/1 2 3/4 5 6/;s#.*#/e/d;/k/s/.*/\&\\n&/#p};' file | sed -f - file

Design a sed script by passing the file twice and applying the sed instructions from the first pass to the second.


Another solution is to use ed:

cat <<\! | ed file
/e/s/1 2 3/4 5 6/
/e/m/k/
wq
!

Or if you prefer:

<<<$'/e/s/1 2 3/4 5 6/\n.m/k/\nwq' ed -s file

Upvotes: 0

dawg
dawg

Reputation: 104062

Here is a GNU awk solution:

awk '
/\<e\>/{
    s=$0
    sub("1 2 3", "4 5 6", s)
    next
}
/\<k\>/ && s {
    printf("%s\n%s\n",$0,s)
    next
} 1
' file

Or POSIX awk:

awk '
function has(x) {
    for(i=1; i<=NF; i++) if ($i==x) return 1
    return 0
}

has("e") {
    s=$0
    sub("1 2 3", "4 5 6", s)
    next
}
has("k") && s {
    printf("%s\n%s\n",$0,s)
    next
} 1
' file

Either prints:

a b c d 1 2 3
i j k l 1 2 3
e f g h 4 5 6
m n o p 1 2 3

This works regardless of the order of e and k in the file:

awk '
function has(x) {
    for(i=1; i<=NF; i++) if ($i==x) return 1
    return 0
}

has("e") {
    s=$0
    sub("1 2 3", "4 5 6", s)
    next
}

FNR<NR && has("k") && s {
    printf("%s\n%s\n",$0,s)
    s=""
    next
}

FNR<NR

' file file

Upvotes: 1

sseLtaH
sseLtaH

Reputation: 11237

Using sed

$ sed '/e/{s/1 2 3/4 5 6/;h;d};/k/{G}' input_file
a b c d 1 2 3
i j k l 1 2 3
e f g h 4 5 6
m n o p 1 2 3

Upvotes: 2

anubhava
anubhava

Reputation: 785721

This awk should work for you:

awk '
/(^| )e( |$)/ {
   sub(/1 2 3/, "4 5 6")
   p = $0
   next
}
1
/(^| )k( |$)/ {
   print p
   p = ""
}' file

a b c d 1 2 3
i j k l 1 2 3
e f g h 4 5 6
m n o p 1 2 3

Upvotes: 0

Related Questions