Pablo
Pablo

Reputation: 29519

How to get unique `uid`?

I'm making a bash script which should create an ftp user.

ftpasswd --passwd --file=/usr/local/etc/ftpd/passwd --name=$USER --uid=[xxx]
     --home=/media/part1/ftp/users/$USER --shell=/bin/false

The only supplied argument to script is user name. But ftpasswd also requires uid. How do I get this number? Is there an easy way to scan passwd file and get the max number, increment it and use it? Maybe it's possible to obtain that number from the system?

Upvotes: 3

Views: 14944

Answers (6)

papo
papo

Reputation: 1949

since this is bash things could get simpler

#!/bin/bash
get_available_uid_basic(){
    local uid_free=1000
    local uids_in_use=( $(cut -d: -f3 < /etc/passwd) )

    while [[ " ${uids_in_use[@]} " == *" $uid_free "* ]]; do
        (( uid_free++ ))
    done
    echo $uid_free
}

uid=$(get_available_uid_basic)
echo $uid

Explanation:
uids_in_use is an array to get rid of new-line characters
there is no "| sort" and it's useless in other answers too
${uids_in_use[@]} is uids_in_use array exploded with spaces as separators
there are spaces before and after first and last array entry, so each entry is separated by spaces on each side
bash's [[ ]] accepts glob character after '=='


System/User uid and first/last available

useradd/adduser has --system argument, this creates user with uid between 100-999
also, useradd does look for available uid starting at 999 going downwards.
In my case I needed both of these behaviors, this is a function which accepts "system" and "reverse" arguments

#!/bin/bash
get_available_uid(){
    local system_range
    [[ $* == *system* ]] && system_range=TRUE
    local reverse
    [[ $* == *reverse* ]] && reverse=TRUE

    local step
    local uid_free

    if [ -n "$system_range" ]; then
        if [ -n "$reverse" ]; then
            uid_free=999
            step=-1
        else
            uid_free=100
            step=1
        fi
    else
        if [ -n "$reverse" ]; then
            uid_free=9999
            step=-1
        else
            uid_free=1000
            step=1
        fi
    fi

    local uids_in_use=( $(cut -d: -f3 < /etc/passwd) )

    while [[ " ${uids_in_use[@]} " == *" $uid_free "* ]]; do
        (( uid_free+=step ))
    done
    
    if [ -n "$system_range" ]; then
        if (( uid_free < 100 )) || (( uid_free > 999 )); then
            echo "No more available uids in range" >&2
            return 1
        fi
    else
        if (( uid_free < 1000 )); then
            echo "No more available uids in range" >&2
            return 1
        fi
    fi

    echo $uid_free
    return 0
}


uid=$(get_available_uid)
echo "first available user uid: $uid"

uid=$(get_available_uid system)
echo "first available system uid: $uid"

uid=$(get_available_uid reverse)
echo "last available user uid: $uid"

uid=$(get_available_uid system reverse)
echo "last available system uid: $uid"


Upvotes: 0

Phidelux
Phidelux

Reputation: 2271

This is a much shorter approach:

#!/bin/bash
uids=$( cat /etc/passwd | cut -d: -f3 | sort -n )
uid=999

while true; do
    if ! echo $uids | grep -F -q -w "$uid"; then
        break;
    fi

    uid=$(( $uid + 1))
done

echo $uid

Upvotes: 1

Jon Zobrist
Jon Zobrist

Reputation: 51

I changed cat /etc/passwd to getent passwd for Giuseppe's answer.

#!/bin/bash
# From Stack Over Flow
# http://stackoverflow.com/questions/3649760/how-to-get-unique-uid
# return 1 if the Uid is already used, else 0
function usedUid()
{
    if [ -z "$1" ]
    then
    return
    fi
    for i in ${lines[@]} ; do
        if [ $i == $1 ]
        then
        return 1
    fi
    done
return 0
}

i=0

# load all the UIDs from /etc/passwd
lines=( $( getent passwd | cut -d: -f3 | sort -n ) )
testuid=999

x=1

# search for a free uid greater than 999 (default behaviour of adduser)
while [ $x -eq 1 ] ; do
    testuid=$(( $testuid + 1))
    usedUid $testuid
    x=$?
done

# print the just found free uid
echo $testuid

Upvotes: 1

bhdnx
bhdnx

Reputation: 520

Instead of reading /etc/passwd, you can also do it in a more nsswitch-friendly way:

getent passwd

Also don't forget that there isn't any guarantee that this sequence of UIDs will be already sorted.

Upvotes: 5

Giuseppe Cardone
Giuseppe Cardone

Reputation: 5393

To get a user's UID:

cat /etc/passwd | grep "^$usernamevariable:" | cut -d":" -f3

To add a new user to the system the best option is to use useradd, or adduser if you need a fine-grained control.

If you really need just to find the smallest free UID, here's a script that finds the smallest free UID value greater than 999 (UIDs 1-999 are usually reserved to system users):

#!/bin/bash

# return 1 if the Uid is already used, else 0
function usedUid()
{
    if [ -z "$1" ]
    then
    return
    fi
    for i in ${lines[@]} ; do 
        if [ $i == $1 ]
        then
        return 1
    fi
    done
return 0
}

i=0

# load all the UIDs from /etc/passwd
lines=( $( cat /etc/passwd | cut -d: -f3 | sort -n ) )

testuid=999

x=1

# search for a free uid greater than 999 (default behaviour of adduser)
while [ $x -eq 1 ] ; do
    testuid=$(( $testuid + 1))
    usedUid $testuid
    x=$?
done

# print the just found free uid
echo $testuid

Upvotes: 2

Fernando Migu&#233;lez
Fernando Migu&#233;lez

Reputation: 11316

To get UID given an user name "myuser":

 cat /etc/passwd | grep myuser | cut -d":" -f3

To get the greatest UID in passwd file:

 cat /etc/passwd | cut -d":" -f3 | sort -n | tail -1

Upvotes: 4

Related Questions