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
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.
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.
I suspect
sed
isn't the thing for this (and others may know how to do it much better withsed
), but here goes:Notes
sed '2,$ s/.$/&,&/'
means duplicate the last character on all but the first line, adding a comma:2,$
from the 2nd line onwardss/old/new/
replaceold
withnew
.$
the last character&
the matched patternsed -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 commandsN
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 patternta
if the lasts
command was successful, branch to:a
and execute the loop again$d
delete the last lineIf 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
You could do this, which also works if there is only one character, of course
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 ofsed
:[^,]*
means zero or more characters that aren't commas.Here's a way to do it in a single sed invocation I think:
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:Note that use of the
-E
(or-r
) extended regex is not a requirement - it just reduces the amount of escaping that's needed.