A variation on steeldriver's answer that anchors the pattern at the end of the line and therefore can use 3
(or whatever number, n
, you need to use) rather than 2
(i.e. n-1
) for the repeat count of trailing fields, as long as there are actually that many fields. It also does not care about the initial data on the line, before the delimiter that we want to replace.
Using standard sed
:
sed 's/;\(\([^;]*;\{0,1\}\)\{3\}\)$/,\1/' file
Using on extended regular expression in sed
implementations that support it:
sed -E 's/;(([^;]*;?){3})$/,\1/' file
Either expression match the last three fields. The sub-expression ([^;]*;?)
will match a string that contains no ;
character, and then optionally ends with one such character. The last field on the line does not end with a ;
, so this allows us to match that field and still anchor the full expression to the end of the line.
This, like most sed
solutions, would not work if any field contains embedded ;
characters or newlines (which are allowed in CSV files).
Using a CSV parser to convert the data into JSON, modifying it with jq
, and then turning it back into a CSV file with the correct delimiters. This would cope with data containing embedded delimiters, newlines, and quotes:
csvjson -H -d ';' file |
jq -r --argjson n 3 '.[] | [.[]] | .[-($n+1):(if $n > 1 then -($n-1) else null end)]] |= [join(",")] | @csv' |
csvformat -H -D ';'
This uses csvkit
to turn the CSV into JSON. It then imports the value 3
into the jq
variable n
on the command line and uses it to join the two fields based on this value. The modified data is then turned back into CSV and reformatted to use ;
as delimiters as from the start.
Given a CSV file with four columns and four rows, like
A;B;C;D
1;"2.1;2.2";3;4
1;2;"3.1
3.2
3.3";4
1;"And then I said ""2 2 2""";3;4
the second delimiter from the end is changed to a comma (effectively merging the 2nd and 3rd fields into a single field):
$ csvjson -H -d ';' file | jq -r --argjson n 2 '.[] | [.[]] | .[-($n+1):(if $n > 1 then -($n-1) else null end)] |= [join(",")] | @csv' | csvformat -D ';'
A;B,C;D
1;"2.1;2.2,3";4
1;"2,3.1
3.2
3.3";4
1;"And then I said ""2 2 2"",3";4