Using Raku (formerly known as Perl_6)
~$ raku -ne 'my @a = .split("|").map: *.split(","); my @b;
given @a.pairs { for .values {
when .key == 0 { @b = $_.value } #(A);
when .value.elems > 1 { @b = [X] @b, .value } #(B);
default { @b .= map( -> $b { $b, .value }) }
}}; put $_ for @b; #say "";' file.txt
Above is an answer written in Raku, a member of the Perl-family of programming languages. Raku features case statements, shown here in the given/when format. These statements are used to create a recipient @b array, which is then output.
In the first statement the input line is split on the pipe-character, then each resulting element is mapped into and further split on commas. Saved into the @a array, this object is used in pair format, which provides .key (indexing) and .value functionality.
Each element is iterated through using a for loop, testing via the following when statements:
#(A): Automatically add the first row to the recipient @b array. FYI, this means the simple code above won't expand multiple elements within the first column (so think of the first column as containing indivisible keys). Add a line-number to your datafile as column 1 to circumvent this limitation,
#(B): When a column contains more than one element, take the @b array and [X] (meta) cross-product expand[1]. Replace @b with this expansion.
default: String-concatenate each cell's value onto the end of every element of the @b array.
Sample Input (OP's file with no-spaces and extra test line):
key1|desc_field|item1,item2,item3,item4|extra_field
key2|desc_field|item1,item2,item3|extra_field
key3|desc_field|item1,item2|itemA,itemB|extra_field
Sample Output (joining columns on spaces, uncomment say "" to add a blank line between input rows):
key1 desc_field item1 extra_field
key1 desc_field item2 extra_field
key1 desc_field item3 extra_field
key1 desc_field item4 extra_field
key2 desc_field item1 extra_field
key2 desc_field item2 extra_field
key2 desc_field item3 extra_field
key3 desc_field item1 itemA extra_field
key3 desc_field item1 itemB extra_field
key3 desc_field item2 itemA extra_field
key3 desc_field item2 itemB extra_field
To join columns on something else (e.g. | space-pipe-space), a fun and powerful way is to declare a new J infix operator, using it alone or in conjunction with the X cross-product operator:
~$ raku -ne 'BEGIN sub infix:<J> ($x, $y) { ($x,$y).join(" | ") };
my @a = .split("|").map: *.split(","); my @b;
given @a.pairs { for .values {
when .key == 0 { @b = $_.value } #(A);
when .value.elems > 1 { @b = @b XJ .value.cache } #(B);
default { @b .= map( -> $b { $b J .value.cache }) }
}}; put $_ for @b; say "";' file.txt
key1 | desc_field | item1 | extra_field
key1 | desc_field | item2 | extra_field
key1 | desc_field | item3 | extra_field
key1 | desc_field | item4 | extra_field
key2 | desc_field | item1 | extra_field
key2 | desc_field | item2 | extra_field
key2 | desc_field | item3 | extra_field
key3 | desc_field | item1 | itemA | extra_field
key3 | desc_field | item1 | itemB | extra_field
key3 | desc_field | item2 | itemA | extra_field
key3 | desc_field | item2 | itemB | extra_field
Remove whitespace and say "" call to get back to a pipe-separated output file sans blank lines.
[1] raku -e 'say <A B C> X 1..3' returns ((A 1) (A 2) (A 3) (B 1) (B 2) (B 3) (C 1) (C 2) (C 3)).
https://docs.raku.org/language/control#index-entry-case_statements_(given)
https://docs.raku.org/language/operators#index-entry-X
https://docs.raku.org/language/functions#Defining_operators
https://raku.org
|inside one of the items? Can you have something likekey2|desc field|"item1|item2",item3|extra field?