7

I have a file with lines, just like this:

A
B
C

I want to create a duplicate file in bash that contains each line merged with the copy of next line, like:

A;B
B;C
C;
  • 2
    Does your input file contain any ; characters? – JigglyNaga May 2 '19 at 11:42
  • No, there is no ; in the input file. – banuj May 2 '19 at 12:01
18

Using awk:

awk 'prev{ print prev ";" $0 }
     { prev = $0 }
     END { if (NR) print prev ";" }'

which with your input, gives

A;B
B;C
C;
| improve this answer | |
16

Quick'n'dirty way (involves reading the file twice):

$ tail -n+2 file | paste -d';' file -
A;B
B;C
C;
| improve this answer | |
  • 1
    It reads the file twice, but every block is read by paste shortly after tail so it will be in cache. Chances are it's going to be more efficient than sed/awk based solutions. One down side is it can't work on pipes. – Stéphane Chazelas May 2 '19 at 12:36
  • 1
    alternatively paste -d';' file <(sed 1d file) – αғsнιη May 4 '19 at 10:25
8
$ sed 'x;G;s_\n_;_;1d;${p;x;s_$_;_;}' file
A;B
B;C
C;

What that sed expression is doing:

  • x: save the incoming line in hold space, and retrieve the previous one
  • G: append the new line (from hold space) to the old one
  • s_\n_;_: replace line-break with a ;.
  • 1d: if this is the first line, delete it (don't print it) and advance to next
  • ${...;}: if this is the last line...
  • p: first print the joined pair
  • x: retrieve the final line
  • s_$_;_: append final ;
| improve this answer | |
6

Somewhat simpler sed solution without hold space:

sed '$!N;y/\n/;/;p;y/;/\n/;D' file
  • $!N to join next line (if any; the $! is not needed with GNU sed when not in POSIX mode)
  • y/\n/;/ replace the newline with ;
  • print the resulting line
  • y/;/\n/ to change back to newline, so with
  • D you can get rid of the first line and continue with the next one
| improve this answer | |
  • That last line is C instead of C; though on the OP's sample. – Stéphane Chazelas May 2 '19 at 12:40
  • Ah, I thought there was an empty last line, but that's just a visual effect of SE formatting, it seems. So we need $!y/;/\n/ if the trailing ; is required. – Philippos May 2 '19 at 12:47
5

Same basic idea as the awk solution given by Torin:

$ perl -lne 'print "$last;$_" if defined $last; $last=$_;END{print "$last;" if $.}' file 
A;B
B;C
C;

Or, if you're into the whole brevity thing:

$ perl -lne'$.>1?print"$l;$_":1;$l=$_}{print"$l;"if$.' file 
A;B
B;C
C;
| improve this answer | |
5

A Vim Solution

One can issue this command (credit to Conspicuous Compiler for suggesting this):

vim "+%s/\n\(.\+\)*/;\1\n\1" sample.txt

Alternatively and probably more customarily, start Vim and open the file and issue the ex command:

:%s/\n\(.\+\)*/;\1\n\1

Explanation:

Substitute the pattern:

  • the new line character,\n
  • a character group,\(.+\), which composes the entire next line. The quantifier which follows, *, just indicates that there can be zero or more matches

with the following:

  • a semicolon
  • followed by a reference to the character group, \1
  • followed by the new line character, \n
  • followed by a second reference to the character group, \1.

Before:
enter image description here

After:

enter image description here

| improve this answer | |
  • It may be helpful to some to have this is a one-liner. e.g. vim '+*ThisReallylongCommand*' – Conspicuous Compiler May 2 '19 at 22:15
  • 1
    You’re missing a + in the one-liner – D. Ben Knoble May 3 '19 at 2:03
  • Oau...such a nice solution for vim. – banuj May 3 '19 at 8:47
  • @ D. Ben Knoble - I fixed the ommission with, +. Thanks – Patrick Bacon May 4 '19 at 21:05
2
$ perl -lp -0777e 's/\n(?=(.*\n?))/;$1/g'  input.txt
| improve this answer | |

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.