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;
3
  • 2
    Does your input file contain any ; characters?
    – JigglyNaga
    May 2, 2019 at 11:42
  • No, there is no ; in the input file.
    – banuj
    May 2, 2019 at 12:01
  • paste -d';' file <(sed 1d file) comment by @αғsнιη
    – Manoj
    Jul 13, 2021 at 11:55

7 Answers 7

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;
16

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

$ tail -n+2 file | paste -d';' file -
A;B
B;C
C;
2
  • 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. May 2, 2019 at 12:36
  • 2
    alternatively paste -d';' file <(sed 1d file) May 4, 2019 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 ;
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
2
  • That last line is C instead of C; though on the OP's sample. May 2, 2019 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, 2019 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;
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

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .