1

I'm trying to loop through 2 groups on macOS and remove users in the admin group if they don't exist in another group.

newadmins=$(dscl . -read Groups/newadmin GroupMembership | cut -c 18-)
adminUsers=$(dscl . -read Groups/admin GroupMembership | cut -c 18-)

for (user in $adminUsers && ! user in $newadmins)
do
        dseditgroup -o edit -d $user -t user admin
        if [ $? = 0 ]; then echo "Removed user $user from admin group"; fi
    else
        echo "Admin user $user left alone"
    fi
done

The above didn't work. I think I'm confusing shell with other languages. Any help would be appreciated. Thank!

The below script worked exactly as expected:

NEW_ADMIN_USERS=$(dscl . -read Groups/newadmin GroupMembership | cut -d ' ' -f 2-)
ADMIN_USERS=$(dscl . -read Groups/admin GroupMembership | cut -d ' ' -f 2-)

DEFUNCT_ADMIN_USERS=$(grep -vxFf <(echo ${NEW_ADMIN_USERS} | tr ' ' '\n') <(echo ${ADMIN_USERS} | tr ' ' '\n'))

for DEFUNCT_ADMIN_USER in ${DEFUNCT_ADMIN_USERS}
do
    if dseditgroup -o edit -d ${defunct_admin_user} -t user admin
  then
    echo "Removed user ${DEFUNCT_ADMIN_USER} from admin group"
  else
    echo "Admin user ${DEFUNCT_ADMIN_USER} left alone"
  fi  
done

Thanks @msbit for all the help!

13
  • 1
    grep -vxF -f file1 file2 Commented Nov 12, 2021 at 3:39
  • 1
    @Jetchisel other way around (I think?). With the long options: grep --invert-match --line-regexp --fixed-strings --file file2.txt file1.txt Commented Nov 12, 2021 at 3:42
  • 1
    @Duetschpire You should add the format of the files if it's not a trivial list of items, one per line. Commented Nov 12, 2021 at 3:43
  • 1
    @Jetchisel All good! I was very pleased to see a grep one liner which sorts out the problem (as originally stated) 👍 Commented Nov 12, 2021 at 3:47
  • 1
    I'm assuming $customadmins should be $newadmins ? Commented Nov 12, 2021 at 4:06

1 Answer 1

1

I would consider doing something like this:

#!/usr/bin/env bash

set -eu

NEW_ADMIN_USERS=$(dscl . -read Groups/newadmin GroupMembership | cut -d ' ' -f 2-)
ADMIN_USERS=$(dscl . -read Groups/admin GroupMembership | cut -d ' ' -f 2-)

DEFUNCT_ADMIN_USERS=$(grep -vxFf <(echo ${NEW_ADMIN_USERS} | tr ' ' '\n') <(echo ${ADMIN_USERS} | tr ' ' '\n'))

for DEFUNCT_ADMIN_USER in ${DEFUNCT_ADMIN_USERS}
do
  if dseditgroup -o edit -d ${DEFUNCT_ADMIN_USER} -t user admin
  then
    echo "Removed user ${DEFUNCT_ADMIN_USER} from admin group"
  else
    echo "Admin user ${DEFUNCT_ADMIN_USER} left alone"
  fi  
done

The main thrust of this is using the grep command put forward by @Jetchisel with process substitution (<()) to prepare a list of admin users in the ADMIN_USERS variable but not in the NEW_ADMIN_USERS variable, then iterating over that variable.

This departs from your approach in a number of ways:

  • setting the errexit and nounset options which will cause the script to exit on any error code from a command, including use of unset variables (set -eu)
  • using the field argument of cut with delimiter set to space when parsing the output of dscl (cut -d ' ' -f 2-)
  • subsequently splitting the list of users into lines with tr (tr ' ' '\n')
  • passing the list through to for as appropriate (using ( was a syntax error, as I suspect the use of ! would be)
  • evaluating the return code of dseditgroup directly as that is what if is testing for
  • removing the trailing fi for the first if command, as it's not needed when you have the else (and would cause a syntax error due to an apparent floating else)

Please test thoroughly, preferably with a dummy command instead of dseditgroup before you're 100% happy that this works as expected, and consider setting the xtrace option (set -x which will echo all the commands as they are executed), while developing.

Sign up to request clarification or add additional context in comments.

9 Comments

this is turning out nicer than I thought... DEFUNCT_ADMIN_USER is returning the correct result, all users who are in ADMIN_USERS but are not in NEW_ADMIN_USERS. But when I run the do it's removing the users who are in both groups
If ${DEFUNCT_ADMIN_USERS} contains the correct values, then it's worth checking that dseditgroup behaves the way you expect, and also worth double checking that the command is being executed as you'd expect (via set -x)
I think I see the problem, and I've updated the answer. Basically, replacing the spaces with newlines before storing in the *_ADMIN_USERS variables wasn't correct, as echo would helpfully print them all out as one line. I've moved that replacement back into the process substitution command. This appears to provide the correct output for ${DEFUNCT_ADMIN_USERS}, but please test to confirm.
Yes, this output is better. I can see now that it won't attempt to remove the users in the ADMIN_USERS. I've changed the command to remove user to: if dscl . -delete /Groups/admin GroupMembership ${DEFUNCT_ADMIN_USER} while this works if I run it on a single user, it doesn't seem to work in the loop. Could it be something to do with formatting the output still? Output (none of the below are in the admin group, I think it's treating all users as a single line): Record was not found. Admin user user15 user20 user21 user22 user25 left alone
Thank you so much for your help! I figured the rest out. I was using #!bin/zsh and some functions aren't supported. #!bin/bash and the dseditgroup command worked perfectly fine. I will add the final script to the main thread for reference.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.