2

I want to convert:

A,p
B,q
C,r
D,s

Into:

A,p,q
B,q,r
C,r,s

Using sed.

3

I suspect sed isn't the thing for this (and others may know how to do it much better with sed), but here goes:

$ sed '2,$ s/.$/&,&/' file | sed -r ':a;N;s/\n(.)(,?.?)(,.)$/\3\n\1\2/;ta' | sed '$d'
A,p,q
B,q,r
C,r,s

Notes

sed '2,$ s/.$/&,&/' means duplicate the last character on all but the first line, adding a comma:

  • 2,$ from the 2nd line onwards
  • s/old/new/ replace old with new
  • .$ the last character
  • & the matched pattern

sed -r ':a;N;s/\n(.)(,?.?)(,.)$/\3\n\1\2/;ta;' means take the last character from each line after the first one and stick it on the end of the previous line:

  • -r use ERE
  • :a label: execute from here
  • ; separates commands
  • N read the next line into the pattern space so we can use \n to represent newlines in the pattern
  • (.) save one character for later
  • ? zero or one of the preceding character
  • $ end of line
  • \1 reference to saved pattern
  • ta if the last s command was successful, branch to :a and execute the loop again
  • $d delete the last line

If your file does not have only single characters between the commas, you won't be able to use the very simple regex above. Here is a version that works if the file is comma separated. For example, given

January,apple
February,pear
March,kiwi
April,mango

You could do this, which also works if there is only one character, of course

$ sed '2,$ s/[^,]*$/&,&/' file | sed -r ':a;N;s/\n([^,]*)(,?[^,]*)(,[^,]*)$/\3\n\1\2/;ta;' | sed '$d'
January,apple,pear
February,pear,kiwi
March,kiwi,mango

sed scripts can be written on multiple lines. I can't claim this significantly improves readability ;) but it may be more portable since there are restrictions on the use of ; in non-GNU versions of sed:

sed '2,$ s/[^,]*$/&,&/' file |
sed -r '{:a
          N
          s/\n([^,]*)(,?[^,]*)(,[^,]*)$/\3\n\1\2/
          ta}' |
sed '$d'

[^,]* means zero or more characters that aren't commas.

  • You can do it in a single invocation I think: sed -nr '$!{:a;N;s/(.*)\n(.*)(,[^,]*)/\1\3\n\2\3/;P;D;ba;}' – steeldriver Jan 10 '18 at 3:19
  • @steeldriver awesome! I'm laughing because it didn't occur to me to reuse \3 etc instead of copying the pattern first! Thanks for teaching how to give a loop an address. I think you should go ahead and post your answer :) – Zanna Jan 10 '18 at 11:37
1

Here's a way to do it in a single sed invocation I think:

sed -nE '$!{:a;N;s/(.*)\n(.*)(,[^,]*$)/\1\3\n\2\3/;P;D;ba;}' file
A,p,q
B,q,r
C,r,s

Explanation

The :a;N;...P;D;ba structure essentially maintains a 2-line buffer, within which we can split the fields and copy/move groups of characters around:

$!{                                   # For any line except the last
  :a                                  # Enter a loop:
  N                                   # Append the following line, after a newline  
  s/(.*)\n(.*)(,[^,]*)$/\1\3\n\2\3/   # Capture (1) up to the newline, 
                                      # (2) from the newline to the last comma, 
                                      # and (3) everything else into groups and 
                                      # copy group 3 before the newline
  P                                   # Print everything up to the newline
  D                                   # Delete everything up to the newline, 
                                      # ready for the next iteration
  ba
}

Note that use of the -E (or -r) extended regex is not a requirement - it just reduces the amount of escaping that's needed.

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.