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

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.