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
  • 252,262
  • 96
  • 511
  • 570

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
  • 69,051
  • 12
  • 96
  • 131
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
  • 223,850
  • 36
  • 205
  • 328
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
  • 172,579
  • 20
  • 259
  • 291
  • +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
  • 77,979
  • 13
  • 71
  • 125
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
  • 252,262
  • 96
  • 511
  • 570
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,335
  • 6
  • 56
  • 99
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,064
  • 23
  • 31
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,623
  • 4
  • 35
  • 51