kniepbert
kniepbert

Reputation: 31

Add item to comma separated list with one-liner

I want to add a new item to a string variable which represents a comma-separated list while preventing to have items twice.

I came up with this python solution:

python -c "import sys ; first = set(sys.argv[1].split(',')) ; all = first.union(set(sys.argv[2:])) ; print ','.join(all)" 1,2 4 3 2
1,3,2,4

In multiple lines:

import sys
first = set(sys.argv[1].split(','))
all = first.union(set(sys.argv[2:]))
print ','.join(all)

While the solution works, it is not pretty well suited for a one-liner in a shell script.

I've tried a couple of things using awk, sed or plain bash but nothing lead to a short but still stable solution. Does anybody has an idea how to express that with a short command line idiom?

Upvotes: 2

Views: 2310

Answers (7)

James Brown
James Brown

Reputation: 37404

More and more AWK:

$ echo 1,2 3 2 4|awk 'BEGIN {RS="[ ,\n]";OFS=","} {a[$0]=++i} END {for (j in a) printf "%s%s", j, a[j]<NR?OFS:ORS}'
1,2,3,4

Upvotes: 0

Michael Vehrs
Michael Vehrs

Reputation: 3363

Using sed:

echo 1,2 3 2 4 | sed -r ':a {s/([^, ]+)(.*)\1/\2,\1/;ta}; s/ /,/g; s/,+/,/g; s/^,//'

Upvotes: 0

James Brown
James Brown

Reputation: 37404

More AWK:

$ cat test.in
1,2 3 2 4
5,4 3 4 5

$ cat test.in|awk 'BEGIN {FS="[ ,]";OFS=","} {delete a; delete b; n=split($0,a,FS); for(i in a) b[a[i]]=n--; for(i in b) printf "%s%s",i,(b[i]>1)?OFS:ORS}'
1,2,3,4
3,4,5

Upvotes: 0

cxw
cxw

Reputation: 17041

A bash option: Keep your list of items in a string variable (list below) with leading and trailing commas. That way every item begins and ends with a comma, which makes things much easier. To add an item:

new_item=42   # or whatever
list="${list//,${new_item},/,}${new_item},"

The ${list//...} removes any duplicates (// means global replacement), then the ${new_item}, pastes the new item onto the end of the list. To add multiple items:

list=",1,2,"
for x in 4 3 2 ; do list="${list//,$x,/,}$x," ; done

To strip the commas off the list, you can use

list="${list#,}"
list="${list%,}"

A one-liner for your specific example above, but extended with more test cases, would be:

bash -c 'list=",$1,"; shift; for f in "$@"; do list="${list//,$f,/,}$f,"; done; list="${list#,}"; list="${list%,}"; echo $list' -- 1,2 3 4 44 444 1 2

Note, though, that most of that line is just getting the list from the arguments and then out to stdout. Inside a script, list="${list//,$x,/,}$x," is all you need.

Upvotes: 1

karakfa
karakfa

Reputation: 67507

awk to the rescue!

$ echo -n "1,2 4 3 2" | awk -v RS='[, ]' '!a[$0]++' | paste -sd,

1,2,4,3

Upvotes: 3

hek2mgl
hek2mgl

Reputation: 157992

It's not a one-liner, but imo still a clean shell solution:

#!/bin/bash
list="1,2"
new_items="4 5 3"

for i in $new_items ; do
    # The `\b` does match at word boundaries
    ! grep -Eq "\b$i\b" <<< "$list" && list="$list,$i"
done
echo "$list"

If you want a one liner from it, you can put it into a function:

function add_items() {
    list="$1"
    new_items="$2"
    for i in $new_items ; do
        ! grep -Eq "\b$i\b" <<< "$list" && list="$list,$i"
    done
    echo "$list"
}

Call it like:

add_items '1,2' '1 2 3'

Upvotes: 1

choroba
choroba

Reputation: 241868

Perl solution:

perl -le 'undef @h{ (split /,/, shift), @ARGV }; $, = ","; print keys %h' 1,2 4 3 2

Upvotes: 1

Related Questions