Multiple conditions in a shell script for loop - bash

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!

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.

Related

Basic bash script issue

Here is my first post on the forum, let me know if I may be more descriptif.
Yesterday, I script a lil' script to start, restart and stop my server in one, so I test 3 times the $1 argument to know if it's start, restart, stop string.
I take all the improvments if I can do it in another way :)
Here is my code :
#!/bin/bash
STA="start"
RES="restart"
STO="stop"
SERVERNAME="server_live"
if [ $1 -ge 1 ]
then
echo "Entre un argument : start, stop, restart"
elif [ $1 = $STA ]
then
screen -mdS $SERVERNAME
screen -S $SERVERNAME -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur redémarré"
elif [ $1 = $RES ]
then
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
screen -mdS $SERVERNAME
screen -S $SERVERNAME -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur Restart"
elif [ $1 = $STO ]
then
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
echo "Serveur Stoppé"
fi
I got the following error :
My code here
It means : syntaxe error near inexpected symbol elif line 10
Thanks in advance.. I wanna add that
#!/bin/bash
echo "read smthg"
read name
put an error too (on read) , how might I know if I got a version issue or something like that ?
you should use case instead of if ... elif, and (almost) always quote your variables between " " :
Please try this version (close to yours, but using case...esac and with some quotes added):
#!/bin/bash
SERVERNAME="server_live"
case "$1" in
start)
screen -mdS "$SERVERNAME"
screen -S "$SERVERNAME" -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur redémarré"
;;
restart)
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
screen -mdS "$SERVERNAME"
screen -S "$SERVERNAME" -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur Restart"
;;
stop)
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
echo "Serveur Stoppé"
;;
*)
echo "argument: '$1' non reconnu..."
exit 1
;;
esac
If you encounter an error, your script may contain things before that part that interfere? Please first paste your script in : www.shellcheck.net and see what it tells you (it will parse it and show a lot of common error, such as unclosed quotes, etc).
EDIT
The problem came from the fact that person was editing under windows (which uses 'CR-LF' line endings) and using it in linux/unix (which expected just LF, and thus took the CR as an additionnal character in the lines containing them).
To get rid of "everything non-printable" that isn't used for scripting in your script:
LC_ALL="C" tr -cd "[$(printf '\t')\$(printf '\n') -~]" <script.bash >script_without_nonprintables.bash
# LC_ALL="C" just before tr makes tr use that environment, which gives "ascii" instead of whatever locale you use. This helps ensure the ranges given are the ascii ones, and not something else.
# -c = complement, ie "whatever is not..." -d="delete", so -cd= "delete whatever is not specified"
# [a-d] = from a to d in the current locale (ascii, here, thanks to LC_ALL="C", so it will be : a, b, c or d
# in ascii, SPACE to ~ covers all the character you need to write scripts, except for TAB (\t) and Newline (\n), so I added those as well.
# for newline, I preceded it with an "\" to have it taken literally
chmod +x script_without_nonprintables.bash

Execute commands from shell script fails

I'm trying read a file which contains lines like this:
Run COMMAND with options "OPTIONS" and arguments "ARGUMENTS"
Then I want to execute this command with given options and arguments. For example I'd like to execute these commands:
Run pwd with options "" and arguments ""
Run ls with options "-al" and arguments "$HOME"
Run ls with options "-al" and arguments "Example: \"strange folder name\""
This is my code
#!/bin/bash
while read -r line
do
COMMAND=$(echo "$line" | cut -d" " -f 2)
OPTIONS=$(echo "$line" | cut -d" " -f 5 | tr -d '"')
ARGUMENTS=$(echo "$line" | cut -d" " -f 8)
$COMMAND $OPTIONS $ARGUMENTS
done <$1
First example is working as it should, second one is giving me error ls: cannot access $HOME: No such file or directory' and third one is not storing the name of the folder to $ARGUMENTS correctly.
second one is giving me error ls: cannot access $HOME: No such file or directory'
This is because the folder named $HOME does not exist. I am not talking about the value of $HOME variable, but the string literal. The shell does not execute the parameter expansion in your situation.
third one is not storing the name of the folder to $ARGUMENTS correctly
This is because -f 8 only extract column 8, try -f 8- to extract the 8th column and all the others until the end of line.
You can give a try to this version below:
while read -r line; do
COMMAND=$(printf "%s" "${line}" | cut -d" " -f 2)
OPTIONS=$(printf "%s" "${line}" | cut -d" " -f 5 | tr -d '"')
ARGUMENTS=$(printf "%s" "${line}" | cut -d" " -f 8-)
$COMMAND $OPTIONS "$(eval printf \"%s\" "$ARGUMENTS")"
done < "${1}"
The eval is a shell built-in command which is used to enable parameter expansion of ARGUMENTS, if applicable.
I have to warn you that the eval is usualy say risky to use.

A script to find all the users who are executing a specific program

I've written the bash script (searchuser) which should display all the users who are executing a specific program or a script (at least a bash script). But when searching for scripts fails because the command the SO is executing is something like bash scriptname.
This script acts parsing the ps command output, it search for all the occurrences of the specified program name, extracts the user and the program name, verifies if the program name is that we're searching for and if it's it displays the relevant information (in this case the user name and the program name, might be better to output also the PID, but that is quite simple). The verification is accomplished to reject all lines containing program names which contain the name of the program but they're not the program we are searching for; if we're searching gedit we don't desire to find sgedit or gedits.
Other issues I've are:
I would like to avoid the use of a tmp file.
I would like to be not tied to GNU extensions.
The script has to be executed as:
root# searchuser programname <invio>
The script searchuser is the following:
#!/bin/bash
i=0
search=$1
tmp=`mktemp`
ps -aux | tr -s ' ' | grep "$search" > $tmp
while read fileline
do
user=`echo "$fileline" | cut -f1 -d' '`
prg=`echo "$fileline" | cut -f11 -d' '`
prg=`basename "$prg"`
if [ "$prg" = "$search" ]; then
echo "$user - $prg"
i=`expr $i + 1`
fi
done < $tmp
if [ $i = 0 ]; then
echo "No users are executing $search"
fi
rm $tmp
exit $i
Have you suggestion about to solve these issues?
One approach might looks like such:
IFS=$'\n' read -r -d '' -a pids < <(pgrep -x -- "$1"; printf '\0')
if (( ! ${#pids[#]} )); then
echo "No users are executing $1"
fi
for pid in "${pids[#]}"; do
# build a more accurate command line than the one ps emits
args=( )
while IFS= read -r -d '' arg; do
args+=( "$arg" )
done </proc/"$pid"/cmdline
(( ${#args[#]} )) || continue # exited while we were running
printf -v cmdline_str '%q ' "${args[#]}"
user=$(stat --format=%U /proc/"$pid") || continue # exited while we were running
printf '%q - %s\n' "$user" "${cmdline_str% }"
done
Unlike the output from ps, which doesn't distinguish between ./command "some argument" and ./command "some" "argument", this will emit output which correctly shows the arguments run by each user, with quoting which will re-run the given command correctly.
What about:
ps -e -o user,comm | egrep "^[^ ]+ +$1$" | cut -d' ' -f1 | sort -u
* Addendum *
This statement:
ps -e -o user,pid,comm | egrep "^\s*\S+\s+\S+\s*$1$" | while read a b; do echo $a; done | sort | uniq -c
or this one:
ps -e -o user,pid,comm | egrep "^\s*\S+\s+\S+\s*sleep$" | xargs -L1 echo | cut -d ' ' -f1 | sort | uniq -c
shows the number of process instances by user.

bash script pulling variables from .txt, keeps giving syntax error while trying to use mount command

Ive been trying to get this to work for the last week and cannot figure out why this is not working. I get mixed results typing directly into the terminal, but keep getting syntax error messages when running from the .sh. using ubuntu 11.10
It looks like part of the mount command gets pushed to the next line not allowing it to complete properly.. I have no idea why this is happening or how to prevent it from going to the second line.
i have several lines defined as follows in mounts.txt, that gets read from mount-drives.sh below
I have called it to run using sudo so it shouldnt be a permissions issue.
Thanks for taking a look, let me know if additional info is needed.
mounts.txt
mountname,//server/share$,username,password,
mount-drives.sh ---origional, updated below
#!/bin/bash
while read LINE;
do
# split lines up using , to separate variables
name=$(echo $LINE | cut -d ',' -f 1)
path=$(echo $LINE | cut -d ',' -f 2)
user=$(echo $LINE | cut -d ',' -f 3)
pass=$(echo $LINE | cut -d ',' -f 4)
echo $name
echo $path
echo $user
echo $pass
location="/mnt/test/$name/"
if [ ! -d $location ]
then
mkdir $location
fi
otherstuff="-o rw,uid=1000,gid=1000,file_mode=0777,dir_mode=0777,username=$user,password=$pass"
mount -t cifs $otherstuff $path $location
done < "/path/to/mounts.txt";
mount-drives.sh ---updated
#!/bin/bash
while read LINE
do
name=$(echo $LINE | cut -d ',' -f 1)
path=$(echo $LINE | cut -d ',' -f 2)
user=$(echo $LINE | cut -d ',' -f 3)
pass=$(echo $LINE | cut -d ',' -f 4)
empty=$(echo $LINE | cut -d ',' -f 5)
location="/mount/test/$name/"
if [ ! -d $location ]
then
mkdir $location
fi
mounting="mount -t cifs $path $location -o username=$user,password=$pass,rw,uid=1000,gid=1000,file_mode=0777,dir_mode=0777"
$mounting
echo $mounting >> test.txt
done < "/var/www/MediaCenter/mounts.txt"
Stab in the dark (after reading the comments). The "$pass" is picking up a newline because the mounts.txt was created in windows and has windows line endings. Try changing the echo $pass line to:
echo ---${pass}---
and see if it all shows up correctly.
There's a lot here that could stand improvement. Consider the following -- far more compact, far more correct -- approach:
while IFS=, read -u 3 -r name path user pass empty _; do
mkdir -p "$location"
cmd=( mount \
-t cifs \
-o "rw,uid=1000,gid=1000,file_mode=0777,dir_mode=0777,username=$user,password=$pass" \
"$path" "$location" \
)
printf -v cmd_str '%q ' "${cmd[#]}" # generate a string corresponding with the command
echo "$cmd_str" >>test.txt # append that string to our output file
"${cmd[#]}" # run the command in the array
done 3<mounts.txt
Unlike the original, this will work correctly even if your path or location values contain whitespace.

Bulk account creation from text file

I have the following script, it reads from users.txt the first and second fields and uses them to generate the username and password and creates the accounts for each line. the problem is that the script is only creating accounts for the first 2 lines and not for the rest
#!/bin/bash
FILE=/home/knoppix/users.txt
USERSH=/bin/bash
while IFS=":" read GECOS USRGRP ; do
groupadd -f $USRGRP
USERNM="u$(cat /dev/urandom| tr -dc '0-9' | fold -w 6| head -n 1)"
USERPW=$(cat /dev/urandom| tr -dc 'a-zA-Z0-9' | fold -w 6| head -n 1)
useradd $USERNM -p $USERPW -g $USRGRP -c "$GECOS,$USRGRP" -d $HOME/$USERNM -s $USERSH -m
ACCNT=$(grep $USRNM /etc/passwd)
echo "${tgrn}Account creation successful!${tr}"
echo "$ACCNT"
echo "Credentials"
echo "${tred}Username:${tr} $USERNM ${tred}Password:${tr} $USERPW"
echo
done < $FILE
#!/bin/bash
while IFS=: read GECOS USRGRP; do
# your groupadd and useradd commands here
done < /home/knoppix/users.txt
#!/bin/bash
for line in $file; do
# make the account
done
rm $file
touch $file

Resources