reduce ssh calls / cleanup

all of our *nix systems are using local user accounts still, so i have come up with a management script to handle useradds / deletions / password resets etc. It is functional, but now i am trying to go through and reduce the number of ssh calls and clean up my messy coding. As it sits right now, this little section could potentially have 8 different ssh connections for just this little function..
I have searched all over to see if there is a way for me to gather more of the information that i am needed to run this code more effeciently (ie less ssh calls) but am having a hard time finding a way to set local variables from remote systems without doing a
VARIABLE=`ssh some_command`
for each one i need to gather

This is just one function in the script so there are things that are ommited such as the gathering of the arrays etc, but if anyone could give me some pointers or ideas for this section, i am sure i could adapt them to the other functions of my script

#!/bin/bash
function delete_user
{
set -x
i=0
for t in ${serverlist_array[@]}; do
SERVER=${serverlist_array[$i]}
# get the servers OS version and type
OSVER=`ssh $USER@$SERVER uname -r`
OSTYPE=`ssh $USER@$SERVER uname -s`
 
# get path to run root commands
if [ $OSVER = "5.10" ]; then
ADMINCMD=/usr/bin/pfexec
elif [ $OSTYPE = "Linux" ]; then
ADMINCMD=sudo
elif [ ! -z `ssh $USER@$SERVER ls /usr/local/bin/sudo` ]; then 
ADMINCMD=/usr/local/bin/sudo
elif [ ! -z `ssh $USER@$SERVER ls /opt/csw/bin/sudo` ]; then
ADMINCMD=/opt/csw/bin/sudo
fi
 
# determine if account is local or not
if ssh $USER@$SERVER grep -w "/export/home/$USERNAME" /etc/passwd
then
LOCALHOME="true"
else
LOCALHOME="false"
fi
 
 
# if the user account doesn't exists, write that out to log file
if [[ -z `ssh $USER@$SERVER cat /etc/passwd | grep -w $USERNAME` ]]; then 
echo "$SERVER Account_Doesnt_Exist" >> /tmp/passwdlist
fi
 
# While the user account exists on this server, create the account
while [[ -n `ssh $USER@$SERVER cat /etc/passwd | grep -w $USERNAME` ]]
do
# Determine is home directory lives local or on nas. Remove if local..
if [ $LOCALHOME = "true" ]; then
echo "Deleting user account AND local home directory on $SERVER" 
ssh -t $USER@$SERVER $ADMINCMD /usr/sbin/userdel -r $USERNAME
else 
echo "Deleting user account on $SERVER"
ssh -t $USER@$SERVER $ADMINCMD /usr/sbin/userdel $USERNAME
echo "PLEASE REMEMBER TO DELETE THE USERS HOME DIRECTORY ON THE HNAS!!"
fi
 
if [ -z `ssh $USER@$SERVER cat /etc/passwd | grep -w $USERNAME` ]; then
echo "$SERVER Account_Deleted" >> /tmp/passwdlist
else
echo "$SERVER Deletion_FAILED!!" >> /tmp/passwdlist
fi
 
done
i=$((i+1))
done
}

You can reduce the number of ssh connections by doing more things with one ssh call. You can run entire scripts, not just single commands:

$ ssh username@host /bin/sh -s a b 3 <<"EOF"

        # Note that 'read' won't work as expected inside the <<"EOF" block
        # because stdin isn't the terminal, it's the script!

        echo
        echo "arg1 is $1"
        echo "arg2 is $2"
        echo "arg3 is $3"

        [ "$3" -gt 1 ] && echo "$3 > 1"

        exit 42
# This EOF must be at the beginning of the line
EOF
Password:

arg1 is a
arg2 is b
arg3 is 3
3 > 1
$ echo "ssh returned $?"
ssh returned 42
$

---------- Post updated at 10:16 AM ---------- Previous update was at 10:11 AM ----------

Also, you have some useless use of cat and useless use of backticks in there.

if [ -z `ssh $USER@$SERVER cat /etc/passwd | grep -w $USERNAME` ]

You don't need 'cat' there. You don't even need to check the output string, just grep's return value, so you don't need backticks either.

This is how I'd simplify some of your code:

ssh username@host /bin/sh -s "$username" <<"EOF"
        if ! /usr/sbin/userdel "$1"
        then
                echo "Couldn't delete remote user"
                exit 1
        fi

        if grep -w "$1" /etc/passwd > /dev/null
        then
                echo "Deleting user failed"
                exit 1
        fi

        echo "Deleting user succeeded"
        exit 0
EOF
1 Like

i appreciate the pointers.. i may just have my scripts built to do things a non-optimal way, but because of the way they are currently designed, your examples dont appear to work for my situation.

My script(s) all run from a single server (doesnt matter which since it is a shared nas mount) and goes out and executes the commands on to the remote systems.. so for your example of

ssh username@host /bin/sh -s "$username" <<"EOF"
        if ! /usr/sbin/userdel "$1"
        then
                echo "Couldn't delete remote user" >> /tmp/passwdlist
                exit 1
        fi
 
        if grep -w "$1" /etc/passwd > /dev/null
        then
                echo "Deleting user failed" >> /tmp/passwdlist
                exit 1
        fi
 
        echo "Deleting user succeeded" >> /tmp/passwdlist
        exit 0
EOF

i am not able get the echo'd information back to the source server that is running it since it is running the echo on the remote system side. I use that info for the synopsis once it is done looping thru all servers..

For the other tip you gave me to reduce my ssh calls, again the issue is that i can echo the arguments, but i can't use that information since the source server knows nothing of the arguments on the remote system.. So my echo will return the uname output, but the source can't use that to determine what is needed to run the commands as root..

although everything you had helped with is functionally correct, it doesnt work for my script layout.. This is most likely an issue of my script(s) not being laid out the best way. Should i instead be pushing the scripts execution off to the remote servers instead of trying to run it all from a single source?

again, i do truly appreciate the help.. i seem to be able to come up with functional scripts, but i am still learning how to make clean, effecient, functional scripts!

If you want it saved on the local side, save it on the local side:

ssh username@host /bin/sh -s "$username" >> /tmp/passwdlist <<"EOF"
        if ! /usr/sbin/userdel "$1"
        then
                echo "Couldn't delete remote user"
                exit 1
        fi
 
        if grep -w "$1" /etc/passwd > /dev/null
        then
                echo "Deleting user failed"
                exit 1
        fi
 
        echo "Deleting user succeeded"
        exit 0
EOF

ok.. that makes sense.. however the issue that i now run into with that, is i get all of the other shell output in addition to the "Deleting user succeded" output

cat /tmp/passwdlist
sh-3.2$ > > > > sh-3.2$ sh3.2$ > > > > sh-3.2$ sh3.2$ Deleting user succeeded

that makes sense, because this is all the info in stdout, but the only thing i need is the echo that says the status

besides coming back around and removing all the extraneous content in the file with something like sed/awk, is there anyway to overcome this?

That's very odd -- a shell run noninteractively really shouldn't be doing that! Try exec /bin/sh instead of just /bin/sh. If that doesn't work try an alternative shell, /bin/ksh or /bin/bash or what have you, instead of basic sh...

Umm that is very odd, you could also try just expanding the variables in the read-from string:

ssh adminuser@host /bin/sh >> /tmp/passwdlist <<EOF
        if ! /usr/sbin/userdel "$deluser"
        then
                echo "Couldn't delete remote user"
                exit 1
        fi
 
        if grep -w "^$deluser" /etc/passwd > /dev/null
        then
                echo "Deleting user failed"
                exit 1
        fi
 
        echo "Deleting user succeeded"
        exit 0
EOF

i was able to get things working properly.. my issue ended up being more with the SSH and sudo interactions and when i needed to use the -t and when i needed to use -Tt.. For some commands, i had to pull them out and move them into their own ssh connection (ie passwd)
In looking at your suggestions, i also was able to get rid of a lot of unneeded testing that i was doing in my script.
I think i have been able to clean up at least 100 lines of unneeded code with your pointer..

so just wanted to say thx!

---------- Post updated at 03:12 PM ---------- Previous update was at 02:57 PM ----------

1 last quick question.. as part of one of my scripts, i am creating the user account.. How do i need to surround the $5 in the bottom code box below in order for it to display the whole comment field? Previously, when using all my numerous ssh calls, i had been using

ssh -t $USER@$SERVER $ADMINCMD /usr/sbin/useradd -u $UIDNUM -g 111 -G 14 -s /bin/ksh -c "'$COMMENT'" $USERNAME

Currently as it sits below, for a comment field that contains "test user" i am only getting test as the comment.. i have tried numerous variations of ", and ' with no luck

ssh -qTt $USER@$SERVER /bin/bash -s "$USERNAME" "$ADMINCMD" "$OSVER" "$UIDNUM" "$COMMENT" >> /tmp/passwdlist <<"EOF"
case "$3" in
5.10 ) # Create the Solaris 10 account with the user in the Primary Admin profile
if ! "$2" /usr/sbin/useradd -u "$4" -g 111 -G 14 -s /bin/ksh -c ""$5"" -P "Primary Administrator" "$1" > /dev/n
ull
then
echo "`hostname`|Couldn't create user account"
exit 1
fi
# Verify account has been created by checking /etc/passwd
if grep -w "$1" /etc/passwd > /dev/null
then
echo "`hostname`|User account created"
exit 1
fi
;;
* ) # Create the account
if ! "$2" /usr/sbin/useradd -u "$4" -g 111 -G 14 -s /bin/ksh -M -c ""$5"" "$1" > /dev/null
then
echo "`hostname`|Couldn't create user account"
exit 1
fi
# If OSVER is greater than 5, assume a linux kernel and put user in wheel and users group
case "$3" in
2.* ) "$2" /usr/sbin/usermod -G 10,100 "$1"
;;
esac
# Verify account has been created by checking /etc/passwd
if grep -w "$1" /etc/passwd > /dev/null
then
echo "`hostname`|User account created"
exit 1
fi
;;
esac
EOF

When you ssh, one layer of quotes gets stripped out. ssh username@host echo "stuff in quotes" becomes echo stuff in quotes . You have to add in literal quote characters to preserve quotes.

ssh username@server echo "\'stuff in quotes\'"

hmmm.. i'm still having issues with that last piece.. could you show me what i am doing wrong in this code? The issue is with the $5 part of the useradd command.. (i added some echo tests at the top to try to help figure out the errors of my ways, and snipped out some case statements for berevity)

ssh -qTt $USER@$SERVER /bin/bash -s "$USERNAME" "$ADMINCMD" "$OSVER" "$UIDNUM" "\'$COMMENT\'" "$LAWSON" "$ROLENAME" "$EXPIRY" "$SERVER" >> /tmp/passwdlist <<"EOF"
case $9 in
* ) echo "\'$5\'" > /tmp/testuser; echo \'$5\' >> /tmp/testuser; echo "'$5'" >> /tmp/testuser
    if ! "$2" /usr/sbin/useradd -u "$4" -s /bin/ksh -c "$5" -d /home/"$1" "$1" > /dev/null
    then    
        echo "`hostname`|Couldn't create user account" 
            #exit 1
    else
        echo "`hostname`|User account created" 
            #exit 1
    fi
    # if expiration is not empty, set on account
    if [ ! -z "$8" ]; then
        "$2" /usr/sbin/usermod -e "$8" "$1"
    fi
    # if user needs to be added to a role, set it here
    if [ ! "$7" = "exclude" ] && [ ! "$7" = "dontexclude" ]; then
        "$2" /usr/sbin/usermod -R "$7" "$1"
    fi
    ;;
esac    

i had added the echo testers and as it sits when using a comment field containing "test user", my current output in /tmp/testuser is

\''test\'
''test'
''test'

and below is how the script for this particular command looks when running it with set -x

+ ssh -qTt user@dellpoc1 /bin/bash -s tester2 /usr/bin/sudo 2.6.18-194.32.1.el5 3033 '\'\''test user\'\''' false dontexclude 12/12/2012 dellpoc1

thanks again! you have been extremely helpful!

I think you've mistaken some double quotes for single quotes.

The inner quotes can be single, but the outer probably should be double -- escaping doesn't even make sense in single quotes.

You don't even need to escape single quotes inside double-quotes, which makes them convenient too.

"'test user'"

---------- Post updated at 08:40 AM ---------- Previous update was at 08:39 AM ----------

When in doubt, you can always just echo a string to see how it'll end up on the other end:

$ echo "'test user'"
'test user'

Much faster than rerunning the whole thing every time :wink:

1 Like

doh! i was looking at the wrong side of things..

set it to "'$COMMENT'" and things are working great...