0

I have a file in VI Editor like this:

I1 a b c d e f 
g h i j k l m     
o p q r s t u v     
w x y z     
I2 a b c d     
e f g h i j k l m     
n o p q r s t     
u v w x y z    
I3 a  b c d e     
f g h i j k l m n     
o p q r s t u v w x y z 

I'm trying to merge the 3 line that are after the line starting with I(^I) like this:

I1 a b c d e f g h i j k l m n o p q r s t u v w x y z     
I2 a b c d e f g h i j k l m n o p q r s t u v w x y z    
I3 a b c d e f g h i j k l m n o p q r s t u v w x y z 

I have googled to see if I can join the 3 lines after the line containing I1 (that is, the line beginning with I [^I]) in VI editor and found the Join command that joins the next line to the current line like :g/^I/norm Jx. But I would like to use this command for joining the next 3 lines to the current line.

It would be highly appreciated if any one can suggest me a method to do this via VI Editor or any scripting language.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
user3641866
  • 45
  • 1
  • 6

8 Answers8

4

Here's one way you could do it using awk:

awk 'NR>1&&/^I[0-9]/{print ""}{printf "%s", $0}END{print ""}' file.txt

When the line number is greater than 1 and the line starts with "I" followed by a digit, use print "" to print a newline. Use printf to print the contents of every line. In the END block (thanks fedorqui), print a final newline.

Testing it out on your file:

$ awk 'NR>1&&/^I[0-9]/{print ""}{printf "%s", $0}END{print ""}' file.txt
I1 a b c d e fg h i j k l mo p q r s t u vw x y z
I2 a b c de f g h i j k l mn o p q r s tu v w x y z
I3 a b c d ef g h i j k l m no p q r s t u v w x y z
Community
  • 1
  • 1
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
3

You're almost there:

:g/^I/norm 4J
  • :g takes a regex and a command
  • :norm 4J is an "ex" command that executes "normal mode" commands. i.e. what you'd type.

See :help :g and :help :norm


Based on Peter's comment: :g/^I/.,+3join or :g/^I/j4

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
2

If you are sure every block has the same number of lines, say 4:

:g/^I/norm 4J

If you can't be sure about the number of lines to join the problem becomes slightly more complex:

:g/^I/norm O    " separates every block with a blank line
:g//vipJ        " join each block into it's own line         
:g/^$/d         " removes every blank line

If you don't mind thinking a little bit outside the box, this command will work with any block size:

:%join|s/ I/\rI/g    " join the whole buffer into one single line
                     " then substitute every ' I' with `\rI` 
romainl
  • 186,200
  • 21
  • 280
  • 313
  • +1 I like the `:join`/`:s` approach myself. If you don't mind the last block not being joined you can do `:g/^I/,//-j` to join arbitrary number of lines between blocks – Peter Rincker Oct 30 '14 at 17:15
  • For joining all the lines, it could be necessary to rotect against 'ignorecase': `%join|s/ \CI/\rI/g` – mMontu Oct 30 '14 at 17:29
2
tr -d "\n" <filename | sed 's/ \+/ /g;s/ I/\nI/g'

Output:

I1 a b c d e f g h i j k l m o p q r s t u v w x y z
I2 a b c d e f g h i j k l m n o p q r s t u v w x y z
I3 a b c d e f g h i j k l m n o p q r s t u v w x y z 
Cyrus
  • 84,225
  • 14
  • 89
  • 153
1

This awk makes it:

awk '/^I[0-9]/ {if (f) print f; f=""} {f=sprintf("%s%s", (f?f FS:""), $0)} END {print f}' file

It keeps adding the lines into a variable f. When a line starting with I + digit is found, it prints it.

for the given input returns:

I1 a b c d e f g h i j k l m o p q r s t u v w x y z
I2 a b c d e f g h i j k l m n o p q r s t u v w x y z
I3 a  b c d e f g h i j k l m n o p q r s t u v w x y z
fedorqui
  • 275,237
  • 103
  • 548
  • 598
1

Perlish answer;

#!/usr/bin/perl

use strict;
use warnings;

while ( <DATA> ) {
   chomp;
   s/\s+/ /g;
   if ( m/^I/ ) { print "\n" };
   print;
}

__DATA__
I1 a b c d e f 
g h i j k l m     
o p q r s t u v     
w x y z     
I2 a b c d     
e f g h i j k l m     
n o p q r s t     
u v w x y z    
I3 a  b c d e     
f g h i j k l m n     
o p q r s t u v w x y z 
Sobrique
  • 52,974
  • 7
  • 60
  • 101
1

A solution in pure bash:

#! /bin/bash

start="I"
cur=
while read line ; do
    if test "${line:0:1}" = "$start" ; then
        test "$cur" = "" || { echo "$cur" ; cur= ;}
    fi
    cur+="$line"
done << EOT
I1 a b c d e f 
g h i j k l m     
o p q r s t u v     
w x y z     
I2 a b c d     
e f g h i j k l m     
n o p q r s t     
u v w x y z    
I3 a  b c d e     
f g h i j k l m n     
o p q r s t u v w x y z 
EOT

echo "$cur"
Edouard Thiel
  • 5,878
  • 25
  • 33
0

On Vim, in addition to the ex commands pointed by glen and romainl, you could record a macro:

qm/^I<enter>vnJq

Then repeat it twice:

2@m

Explanation

  • qm - start recording a macro on a given register (mon this case)
  • /^Ienter - search for regex ^I
  • v - start visual selection
  • n - select text before the next occurrence of the regex (this step and the previous could be replaced for V3j if the number of lines is fixed)
  • J - join selected lines
  • q - stop recording the macro
mMontu
  • 8,983
  • 4
  • 38
  • 53