Case 11. Grouping of personnel

Keywords: Linux shell vim less

The requirements are as follows:

1) Assume that there are 72 persons in total (providing a list of persons with the name of Chinese Pinyin)

2) Seven groups

3) To ensure the randomness of the personnel distribution group, that is, the results of each execution of the script should be different.


Knowledge Point 1: Generating Random Numbers

In Linux system, there is a built-in variable RANDOM, which is actually a random number. The usage is as follows:

# echo $RANDOM
17023

This RANDOM variable ranges from 0 to 32767. If you want to get a longer random number, you can achieve it by adding multiple RANDOM variables, for example:

# echo $RANDOM$RANDOM
2239928526

The second method is to use the date command. The date has a% N, which can print the second of the current time in nanoseconds:

# date +%N
594253932

The third method is to read the system's special device / dev/urandom, as follows:

# head -100 /dev/urandom|cksum
2604766758 24408

This / dev/urandom is a special device that generates random numbers. It's binary, so you can't cat or head directly, but you can add pipes to deliver the results to cksum. Cksum is a tool for checking files or characters, which generates two numeric strings. Because the string in front of the pipe is random, the number string is also random.

We just need to get the first number string, so we need awk to deal with it.

# head -100 /dev/urandom|cksum|awk '{print $1}'
2194071486

Of course, the cksum command can also calculate the check value of a string to get a random number, as follows:

# echo "Zhangsan"|cksum |awk '{print $1}'
2674179083


Knowledge Point 2: Disorder

Because the case requires randomness, the best way to give the list of people is to disrupt the order, so as to ensure that each script execution has a certain randomness, you can use the shuf command to achieve:

# seq 1 5 |shuf
2
4
5
1
3
# seq 1 5 |shuf
3
4
5
1
2

Each time you get a different sequence. It can also act directly on the file, which will disrupt the line order of the file:

# shuf /etc/passwd |head -n2
user_82:x:1102:100::/home/user_82:/bin/bash
user_93:x:1113:100::/home/user_93:/bin/bash
# shuf /etc/passwd |head -n2
user_13:x:1033:100::/home/user_13:/bin/bash
user_31:x:1051:100::/home/user_31:/bin/bash

Note that the shuf command simply scrambles the line order and does not change the position of the characters in the line.


Knowledge Point 3: Number Rounding in shell

Starting operations in the shell will directly discard decimal numbers, such as 1.9 to 1, which is obviously unreasonable. The following method can achieve rounding.

#!/bin/bash
#The following functions are for division operations and limit $1 to dividend and $2 to divide.
div()
{
  n=`echo "scale=1;$1/$2" |bc`
  n1=`echo "sclae=1;$n+0.5" |bc`
  echo $n1 |cut -d . -f1
}
div 10 3


List of testers for this case

# vim member.txt

xiaoguisheng
guoyuqing
xiongyongzheng
mengjintang
chaizuzhou
zhousheng
xufangming
zhaoliangyun
hanshiru
wangxianyi
zhangjipei
luxiuli
yangshugen
guoyongzhi
lijianguo
wuqiongchen
dinglin
yaoyashan
yinzijia
wangbencheng
liuxiuwen
chenzuqi
leyuguo
baozongyao
fenghao
sunxiaoquan
zhangyaxian
lijiuzhe
dulichun
lixi
shenpeiwen
zousilin
luoping
chaiyan
fandaozhang
huzixiang
jinzhen
zhujunfeng
liqianbiao
hangyanliang
luorenjian
loujianji
fujianzhou
gengyiwu
jinjigui
liuzhizhong
lisanyan
lisili
zhangyiyu
songguozhen
zhangxinghua
zhaozhiyong
huanghe
xiaojie
fanhongfei
wangguiwen
renshumin
songfuying
zhanghaibo
liguangqun
puaihua
yanzhihua
gaojixian
liulai
funing
chenruizhi
chendaxin
laishaoying
xujian
xiaozhekou
xuxiaping
jiangchunqing

Reference script for this case

#!/bin/bash
#Divide people into groups
#Authors:
#Date:
#Version: v1.0

#Personnel List File
f=member.txt
#Number of Groups
group_n=7
#Total number of personnel
member_n=`wc -l $f|awk '{print $1}'`

#Calculate the id of the user's group by name
get_n()
{
    #Calculate cksum value by name
    l=`echo $1|cksum|awk '{print $1}'`
    #Get a random number
    n1=$RANDOM
    #The cksum value is added to the random number and then divided by the number of groups to make sure that the remainder is different each time.
    n2=$[$n1+$l]
    g_id=$[$n2%$group_n]
    #If the number of groups is 7, the remainder range is 0-6, and if the remainder is 0, the group is 7.
    if [ $g_id -eq 0 ]
    then
        g_id=$group_n
    fi
    echo $g_id
}

for i in `seq 1 $group_n`
do
    #n_$i.txt is a temporary document for recording members of the group
    #If the script has been executed before, the file will exist. The temporary file should be deleted before the script is executed this time.
    [ -f n_$i.txt ] && rm -f n_$i.txt
done


shuf $f|while read name
do
    #Calculate the id of the user's group
    g=`get_n $name`
    #Adding people to his team
    echo $name >> n_$g.txt
done

#Define a function that calculates the number of rows in a file
nu(){
    wc -l $1|awk '{print $1}'
}

#Group with the largest number of members
max(){
    ma=0
    for i in `seq 1 $group_n|shuf`
    do
        n=`nu n_$i.txt`
        if [ $n -gt $ma ]
        then
            ma=$n
       fi
    done
    echo $ma
}

#Get the group with the least number of members
min(){
    mi=$member_n
    for i in `seq 1 $group_n|shuf`
    do
       n=`nu n_$i.txt`
       if [ $n -lt $mi ]
       then
           mi=$n
       fi
    done
    echo $mi
}

#Define rounding function
div()
{
    n=`echo "scale=1;$1/$2"|bc`
    n1=`echo "scale=1;$n+0.5"|bc`
    echo $n1|cut -d. -f1
}

#Average group membership (non-rounded)
ava_n=$[$member_n/$group_n]
#Average group membership (rounded)
ava_n1=`div $member_n $group_n`

if [ $ava_n -eq $ava_n1 ]
then
    #Define the initial minimum
    ini_min=1
    #Here's what the while cycle does, which is to get people from large groups into small groups.
    #The condition of this while cycle is that the minimum number of group members is less than the average number of group members.
    while [ $ini_min -lt $ava_n1 ]
    do
        #Find out the group with the largest number of people
        m1=`max`
        #Find the group with the least number of people
        m2=`min`
        for i in `seq 1 $group_n|shuf`
        do
            n=`nu n_$i.txt`
            #Find the file f1 corresponding to the group with the largest number of people (there may be more than one, just take the first one that appears here)
            if [ $n -eq $m1 ]
            then
                f1=n_$i.txt
            #Find the file f2 corresponding to the group with the least number of people (there may be more than one, just take the first one that appears here)
            elif [ $n -eq $m2 ]
            then
                f2=n_$i.txt
            fi
        done
        #Take the last person's name in f1
        name=`tail -n1 $f1`
        #Add this name to f2
        echo $name >> $f2
        #Delete the name just taken from f1
        sed -i "/$name/d" $f1
        #Assign ini_min the minimum number of people at this time
        ini_min=`min`
    done
else
    #Define the initial maximum
    ini_max=$member_n
    while [ $ini_max -gt $ava_n1 ]
    do
        #Find out the group with the largest number of people
        m1=`max`
        #Find the group with the least number of people
        m2=`min`
        for i in `seq 1 $group_n|shuf`
        do
            n=`nu n_$i.txt`
            #Find the file f1 corresponding to the group with the largest number of people (there may be more than one, just take the first one that appears here)
            if [ $n -eq $m1 ]
            then
                f1=n_$i.txt
                #Find the file f2 corresponding to the group with the least number of people (there may be more than one, just take the first one that appears here)
            elif [ $n -eq $m2 ]
            then
                f2=n_$i.txt
            fi
        done
        #Take the last person's name in f1
        name=`tail -n1 $f1`
        #Add this name to f2
        echo $name >> $f2
        #Delete the name just taken from f1
        sed -i "/$name/d" $f1
        #Assign ini_min the minimum number of people at this time
        ini_max=`max`
    done
fi

for i in `seq 1 $group_n`
do
    echo -e "\033[34m$i The members of the group are:\033[0m"
    cat n_$i.txt
    #Delete temporary files
    rm -f n_$i.txt
    echo
done


Posted by ronverdonk on Fri, 09 Aug 2019 00:12:25 -0700