how to count number of fields for a single column in a row separated by 2 field separators (":" and ",")?

Viewed 82

given the text file:
(the structure is: "group_name:pw:group_id:user1<,user2>...")

adm:x:4:syslog,adm1
admins:x:1006:adm2,adm12,manuel
ssl-cert:x:122:postgres
ala2:x:1009:aceto,salvemini
conda:x:1011:giovannelli,galise,aceto,caputo,haymele,salvemini,scala,adm2,adm12
adm1Group:x:1022:adm2,adm1,adm3
docker:x:998:manuel

how can i count the number of users for every line? or for a single line?

for example, if i want to know how many users contains the "adm1Group", the output should be 3, because adm1Group has three users (adm2, adm1 and adm3). another example, the first line (group name "adm"), contains two users, syslog and adm1.

the main problem is that there are two field separators here, so how can i separate the $4 column inside the same awk command? i have this solution by me but here i use two different awk commands linked with a pipe, like this (and i don't know if this is correct or "legal" for the kernel):

awk -F: '/adm1Group/ {print $4}' file.txt | awk -F, 'BEGIN {printf "N. of users in adm1Group = "} {print NF}'

can i achieve a solution like this in a single awk command? if not, can i use this? or this solution is "bad practice"?

5 Answers

how can i count the number of users for every line? or for a single line?

I would use GNU AWK to count number of , inside 4th field and increase it by 1, let file.txt content be

adm:x:4:syslog,adm1
admins:x:1006:adm2,adm12,manuel
ssl-cert:x:122:postgres
ala2:x:1009:aceto,salvemini
conda:x:1011:giovannelli,galise,aceto,caputo,haymele,salvemini,scala,adm2,adm12
adm1Group:x:1022:adm2,adm1,adm3
docker:x:998:manuel

then

awk 'BEGIN{FS=":"}{printf "N of users in %s is %s\n", $1, gsub(/,/,"",$4)+1}' file.txt

gives output

N of users in adm is 2
N of users in admins is 3
N of users in ssl-cert is 1
N of users in ala2 is 2
N of users in conda is 9
N of users in adm1Group is 3
N of users in docker is 1

Explanation: I inform GNU AWK that field separator (FS) is :. For each line I do use printf which acts like fill template and print and for filling I use 1st field ($1) and number of changes gsub function done when ordered to replace , using empty string ("") at 4th field ($4) increased by 1 (as last name has not trailing ,). Note that this does alter $4 (delete , characters) but for this task said side effect is irrelevant. Note that when using printf you need to provide newline character (\n) implicitly, as opposed to print.

(tested in gawk 4.2.1)

You can use split for this:

awk -F: '$1 == "adm1Group" {print split($NF, a, /,/)}' file
3


awk -F: '$1 == "conda" {print split($NF, a, /,/)}' file
9

Or to print all of them together:

awk -F: '{print split($NF, a, /,/), "no of users in adm1Group:", $1}' file

2 no of users in adm1Group: adm
3 no of users in adm1Group: admins
1 no of users in adm1Group: ssl-cert
2 no of users in adm1Group: ala2
9 no of users in adm1Group: conda
3 no of users in adm1Group: adm1Group
1 no of users in adm1Group: docker

With your shown samples and attempts please try following awk code. This will print total number of users present in each group name for your Input_file.

awk -F':' '
{
  num=0
  arr1[$1]=num=split($NF,arr2,",")
}
END{
  for(i in arr1){
    print "Group " i " has " arr1[i] " users."
  }
}
' Input_file

Explanation: Adding detailed explanation for above code.

awk -F':' '                          ##Starting awk program where setting field separator as : here.
{
  num=0                              ##Setting num as 0 here.
  arr1[$1]=num=split($NF,arr2,",")   ##Creating arr1 array with index of $1 and has value of num, which contains total number of total elements in arr2 with delimiter of , here.
}
END{                                 ##Starting END block of this program from here.
  for(i in arr1){                    ##Traversing through arr1 here.
    print "Group " i " has " arr1[i] " users."  ##printing group name and its value(how many times users came for that group).
  }
}
' Input_file                         ##Mentioning Input_file name here.

Use : or , as the field separator, then print the number of fields minus the 3 leading ones:

awk -F'[:,]' '{print $1, NF - 3}' file
awk -F'[:,]' -v group=conda '$1 == group {print NF - 3}' file
{m,g}awk '$!NF=sprintf("%20s\47s user(s) count = %\0478.f",$!_,NF-!_)' FS=':.+:|,'

          adm's  user(s) count =         2
       admins's  user(s) count =         3
     ssl-cert's  user(s) count =         1
         ala2's  user(s) count =         2
        conda's  user(s) count =         9
    adm1Group's  user(s) count =         3
       docker's  user(s) count =         1

And with a tiny bit of modification, now the full list of users will be available at the tail. specifically, the tiny bolded item - now it's overwriting $1 instead of $0 ::

{m,g}awk ' $!_ = sprintf("%15s\47s user(s) count = %\0476.f",$!_,NF-!_)' FS=':.+:|,'

        adm's user(s) count =      2 syslog adm1
     admins's user(s) count =      3 adm2 adm12 manuel
   ssl-cert's user(s) count =      1 postgres
       ala2's user(s) count =      2 aceto salvemini
      conda's user(s) count =      9 giovannelli galise aceto caputo haymele salvemini scala adm2 adm12
  adm1Group's user(s) count =      3 adm2 adm1 adm3
     docker's user(s) count =      1 manuel
Related