Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

How can we write a script around dscl to loop over the currently listed IDs in use and then spit out the first id under 500 that is not in use yet?

Update # 1 (Feb 17th 2013)

I found some very helpful scripts on http://wiki.awkwardtv.org/wiki/Manage_users_and_groups_scripts which I was able to water down to the point where I could get the first available ID higher than a given number but I still don't have a way of scripting it to stop looking beyond a certain upper limit like 500.

#!/bin/sh
continue="no"
number_used="dontknow"
fnumber=300
user_id=0
until [ $continue = "yes" ] ; do
  if [ `dscl . -list /Users UniqueID | awk '{print $2, "\t", $1}' | sort -ug | grep -c "$fnumber"` -gt 0 ] ; then
    number_used=true
  else
    number_used=false
  fi
  if [ $number_used = "true" ] ; then
    fnumber=`expr $fnumber + 1`
  else
    user_id="$fnumber"
    continue="yes"
  fi
done;
echo "Next available user_id: $user_id"

Update # 2 (Feb 17th 2013)

I suppose I could work backwards but still what if every userid from 500 to 0 is taken? I still need to set a lowerbound to get out of a messy negative ID situation.

#!/bin/sh
continue="no"
number_used="dontknow"
fnumber_work_backwards_from=500
fnumber=$fnumber_work_backwards_from
user_id=0
until [ $continue = "yes" ] ; do
  if [ `dscl . -list /Users UniqueID | awk '{print $2, "\t", $1}' | sort -ug | grep -c "$fnumber"` -gt 0 ] ; then
    number_used=true
  else
    number_used=false
  fi
  if [ $number_used = "true" ] ; then
    fnumber=`expr $fnumber - 1`
  else
    user_id="$fnumber"
    continue="yes"
  fi
done;
echo "First available user_id which is closest to and lower than $fnumber_work_backwards_from: $user_id"
share|improve this question
    
Did you see my answer when you updated the question? (I undeleted it, maybe it didn't show up) –  slhck Feb 17 '13 at 15:56

2 Answers 2

up vote 0 down vote accepted

This can be done with Ruby, for example, which is much more concise than any Bash script you could possibly find for this.

dscl . -list /Users UniqueID | awk '{print $2}' | 
ruby -e 'puts ((0..500).to_a - STDIN.readlines.map(&:to_i)).first'

We simply subtract the actual IDs (as an array) from another array consisting of the numbers 0 through 500. This gives us all unused IDs in an array, and from that we take the first, since it's already sorted.

share|improve this answer
    
Not a language I can use in my infrastructure but you answered the Q :) –  pulkitsinghal Feb 18 '13 at 15:46
    
Oh, I didn't know that, sorry. OS X comes with Ruby though, or can't you use it for some other reason? Note that going with a set-based approach is often easier than lower level stuff—problem domain thinking, so to say :) –  slhck Feb 18 '13 at 17:01

This should work to find what you need. The function find_next_userid below will search between two numbers, inclusively, to find the first unused userid. For your case, just use 0 and 499, shown below. The function returns an empty string if it couldn't find any empty slots, so you need to check for that before using the echoed value (shown below as well).

I realize this does not use dscl, but this should work and be more portable. [Update: Added version using dscl.]

Technically, I am using bash, so I have noted that; I'm not sure if would work in sh or not.

Tested on OS X 10.9.4.

#!/bin/bash
user_exists()
{
    username=$1

    if id -u $username >/dev/null 2>&1; then
        return 0
    else
        return -1
    fi
}

user_exists_dscl()
{
    username=$1

    user_found=`dscl . -search /Users UniqueID $username | awk 'FNR == 2 {print $1}'`
    if [ "${user_found}" != "" ] ; then
        return 0
    else
        return -1
    fi
}

find_next_userid()
{
    low=$1
    hi=$2

    for curr in `seq $low $hi`;
    do
        if ! (user_exists $curr) ; then
            echo $curr
            break
        fi
    done

    echo ""
}

FIRST_UNUSED_USERID=`find_next_userid 0 499`

if [ "$FIRST_UNUSED_USERID" != "" ]; then
    echo "Do your stuff here!"
else
    echo "No unused userid found!"
fi

Edit: Forgot to include user_exists(). Updated to note that this solution doesn't use dscl but still works. I think I might have a solution using dscl shortly.

Edit 2: Added user_exists_dscl. Replace calls to user_exists with user_exists_dscl if you really really need to use dscl instead of id.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.