Return variable from while loop in bash - bash

I am trying to use a while loop to repeatedly prompt for a username during user registration until a valid username has been provided (i.e. it has NOT already been claimed).
Unfortunately, I've only been coding in bash for less than 70 hours now, so I wasn't familiar with how to do this. My initial thought was to use "goto" like in Windows batch scripts if there was a problem with the username, but apparently that's not a thing in bash and loops seemed to be the recommended route. I wasn't familiar with loops in bash so I did some research until I found a good answer on SO about how to do this. This isn't the exact post (I can't find it now), but I was looking at questions like this one.
The resulting code looks like this:
echo -e "Now, choose a unique username. No special characters, please."
uniqueuser=0 # Assume a valid username has not been chosen from the get-go
while [ "$uniqueuser" == "0" ]
do
read -p "Username: " username
lusername=`echo $username | sed y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/` #lowercase username
if [ "$lusername" == "admin" ] || [ "$lusername" == "administrator" ] || [ "$lusername" == "npstn" ] || [ "$lusername" == "phreak" ] || [ "$lusername" == "guest" ] || [ "$lusername" == "user" ] || [ "$lusername" == "sysop" ]
then
echo -e "That username is reserved. Please pick another username."
else
username=`php /home/com/encode.php "$username"`
available=`curl "https://example.com/redacted/redacted.php?key=$key&type=checkusername&username=$username" --silent`
if [ "$available" == "1" ]
then
uniqueuser=$((1)) # Username is unique and acceptable
else
echo -e "That username is taken. Please choose another one."
fi
fi
done <input.data # so that username variable persists outside of while loop
echo -e "That username is available... or was... now, it's yours!"
echo -e "On this board, we have no annoying password length or complexity requirements. That said, your password cannot be null, nor can it use the plus symbol. Choose wisely!"
When this part of the shell script is reached, I see:
Now, choose a unique username. No special characters, please.
/home/com/redacted.sh: line 4: input.data: No such file or directory
That username is available... or was... now, it's yours!
On this board, we have no annoying password length or complexity requirements. That said, your password cannot be null, nor can it use the plus symbol. Choose wisely!
The reason "input.data" is used is to get the $username variable out from the subshell and back into the main shell. However, that seems to be causing an error. How can I fix this?

You've misread the sources on which you're relying. Redirecting stdin from a file, as in:
count=0
while IFS= read -r line; do
(( ++count )) # or do something else
done < input.data
echo "$count" # will print number of lines in the file input.data
only prevents a subshell from being created when the alternative is redirecting stdin from a pipe. For example, you would indeed lose the content of variables you set in:
# DO NOT DO THIS; this is the practice the other answers were teaching how to avoid.
count=0
cat input.data | while IFS= read -r line; do
(( ++count )) # or otherwise modify data within the loop
done
echo "$count" # will print 0, because the loop was in a subshell
This change is not necessary, and not appropriate, when your input is from stdin and not from a file.
It's the cat input.data | that is a problem, for which < input.data is a replacement. If your original code had no cat input.data |, then you need not use < input.data to replace it.
Demonstrating that a while read loop can read from stdin, and retain its variables' values, without any such redirection:
count=0
prompt='Enter a string, or a blank line to stop: '
while IFS= read -r -p "$prompt" line && [[ $line ]]; do
(( ++count ))
printf 'Line %d: %s\n' "$count" "$line"
done
echo "Final counter is $count"
...keeps the same value for $count outside the loop it hand inside the loop, when a blank line is entered.

Related

Difficulty in mentioning multiple calls in a single line of echo

The problem I have is with echo cannot echo e.g: "$prefix_$suffix". This is a assignment for a class in school if that changes things.
I've tried e.g "$prefix _$suffix" but that creates a space between the prefix and suffix
#!bin/bash
read -p "Username prefix: " prefix
read -p "Amount of users: " amount
read -p "Name of file to store, include extension (e.g test.txt): " filename
touch "$filename"
new="$amount"
suffix=0
state=true
while [ state=true ] ; do
#in this function i reverse the user input amount of users so it appears as user 1,2,3 (and so on) in the first line of the text file that is also user input.
if [ "$new" -ge 1 ] ; then
newpass="$(gpg --gen-random --armor 1 12)"
#reversing process, making the suffix start at 1 so user 1 gets assigned suffix 1 for the username and i decrease the "new" variable that gets set to "$amount" so the while loop isn't indefinite
new=`expr "$new" - 1`
suffix=`expr "$suffix" + 1`
echo -n "$prefix" >> "$filename"
echo -n "_$suffix" >> "$filename"
echo -n " " >> "$filename"
echo "$newpass" >> "$filename"
echo -e >> "$filename"
elif [ "$new" -eq 0 ] ; then
break
fi
done
a run of this bash results in 5 lines e.g:
re_1 UlrZW3jB5L9zt6Nf
and so on, depending how many users you choose at the input
however the next task is to create users with the username, in this example re_1 with the password: UlrZW3jB5L9zt6Nf. This is where the clunky echo stuff I've done doesn't work. I tried doing useradd -u "$prefix_$suffix" and "$prefix $suffix" , none of these work since "$prefix$suffix" is treated as one call instead of two and the "$prefix _$suffix" adds one space in between the prefix and suffix which is not acceptable.
Even if this looks very introverted to you, hence i added comments to make it understandable, help is very appreciated.
Feel free to ask question if you do not understand and want to help!
This will do what you want:
echo "${prefix}_${suffix}"

How to remove contact from shell script?

I am creating a simple phonebook using unix shell scripts. I have gotten all of my functions to work except the removal of a contact after it has been created. I have tried combining grep and sed in order to accomplish this, but cannot seem to get over the hump. The removal shell i've tried is as follows.
#!/bin/sh
#removeContact.sh
echo “Remove Submenu”
echo “Please input First Name:”
read nameFirst
echo “Please input Last Name:”
read nameLast
x=$(grep -e “$nameFirst” -e “$nameLast” ContactList)
echo $x
sed '/'$x'/ d' ContactList;
echo “$nameFirst $nameLast is removed from your contacts”
exit 0
I'm not sure if I am declaring x incorrectly, or if my syntax is wrong when sed is used.
Any help would be greatly appreciated. Thank you.
#!/bin/bash
ContactList="contacts.txt"
export ContactList
exit=0
while [ $exit -ne 1 ]
do
echo "Main Menu"
echo "(a) Add a Contact"
echo "(r) Remove a Contact"
echo "(s) Search a Contact"
echo "(d) Display All Contact’s Information"
echo "(e) Exit"
echo "Your Choice?"
read choice
if [ "$choice" = "a" ]
then
./addContact.sh
elif [ "$choice" = "r" ]
then
./removeContact.sh
elif [ "$choice" = "s" ]
then
./searchContact.sh
elif [ "$choice" = "d" ]
then
./displayContact.sh
elif [ "$choice" = "e" ]
then
exit=1
else
echo "Error"
sleep 2
fi
done
exit 0
#!/bin/sh
#addContact.sh
ContactList="contacts.txt"
echo “Please input First Name:”
read nameFirst
echo “Please input Last Name:”
read nameLast
echo “Please input Phone Number:”
read number
echo “Please Input Address”
read address
echo “Please input Email:”
read email
echo $nameFirst:$nameLast:$number:$address:$email>> ContactList;
echo "A new contact is added to your book."
exit 0
sed '/'$x'/ d' ContactList
won't remove anything from the file ContactList, it will simply output the changes to standard output.
If you want to edit the file in-place, you'll need the -i flag (easy) or to make a temporary file which is then copied back over ContactList (not so easy, but needed if your sed has no in-place editing option).
In addition, since ContactList is a shell variable referencing the real file contacts.txt, you'll need to use $ContactList.
And, as a final note, since you're using the full line content to do deletion, the presence of an address like 1/15 Station St is going to royally screw up your sed command by virtue of the fact it contains the / character.
I would suggest using awk rather than sed for this task since it's much better suited to field-based data. With the record layout:
$nameFirst:$nameLast:$number:$address:$email
you could remove an entry with something like (including my patented paranoid perfect protection policy):
cp contacts.txt contacts.txt.$(date +%Y.%m.%d.%H.%M.%S_$$)
awk <contacts.txt >tmp.$$ -F: "-vF=$nameFirst" "-vL=$nameLast" '
F != $1 || L != $2 {print}'
mv tmp.$$ contacts.txt

Issue with all conditions (configuration profiles) being checked

Ok, so now I have it setup like this but still it is giving me a ; exit;
logout
[Process completed]
when run through the terminal I know that there are configuration profiles that are not on my computer that should make the script continue or rather to keep looping. What is this not happening?
Thanks in advance....!
Here is what i have so far:
#!/bin/bash
profilesInstalled=`profiles -P|awk '/attribute/ {print $4}'`
while read line ; do
if [ "$line" = "F2CC78D2-A63F-45CB-AE7D-BF2221D41218" ];then
echo "AD Binding is present"
elif [ "$line" = "1C94DAD1-5FC7-46CE-9E09-576841C15093" ];then
echo "Energy Saver is present"
elif [ "$line" = "A0E5B977-F0AF-44C9-8001-DA0511B702B8" ];then
echo "Finder is present"
elif [ "$line" = "5E9DE5BF-34E4-4A7F-AA29-461FB0631943" ];then
echo "FV2 Redirect is present"
elif [ "$line" = "9AE91C88-D1B2-4227-9E95-80F492DCAA11" ];then
echo "Login Window/Security and Privacy is present"
elif [ "$line" = "00000000-0000-0000-A000-4A414D460003" ];then
echo "MDM Profile is present"
elif [ "$line" = "5E85BBF0-3483-4C80-A1FC-70AF20F82E7C" ];then
echo "Restrictions is present"
elif [ "$line" = "E433D546-5502-4C3F-9E5F-4732ED1F0032" ];then
echo "SAC SUBCA-01 is present"
elif [ "$line" = "5C2AE16B-D4E9-4D15-B190-3CD7B28779E8" ];then
echo "SAC SUBCA-02 is present"
elif [ "$line" = "2C620A13-DF1E-4F6A-A32B-9FA3149F8A56" ];then
echo "SAC-CA-01 is present"
elif [ "$line" = "3B44AE14-E0CE-4621-BACF-1A9C3BA4A459" ];then
echo "Screensaver is present"
elif [ "$line" = "396A9D84-A9CA-4575-8D09-C9F054B76AF7" ];then
echo "Spotlight is present"
elif [ "$line" = "E0138F02-9A15-47BD-8CA5-7D1D0985A1A6" ];then
echo "Workday Corp is present"
fi
exit 0
done <<<"$profilesInstalled"
You need a space around the = in those tests. That first test will always pass as written.
You should also quote the "$line" variable expansion.
Unless you use $profilesInstalled somewhere else you don't need that variable at all and can just pipe the profiles pipeline to the while loop directly.
You can also replace grep in that pipeline with awk '/attribute/ {print $4}'.
Some "meta" remarks first:
Please don't change your original question substantially, as that can invalidate existing answers (such as Etan Reisner's helpful answer)
Instead, add later changes to the original question and mark the changes as such.
If a different (follow-up) question arises, ask it as a separate, new question.
Please learn how to format your code properly - it was done for you, and you managed to destroy that formatting again with your later edits.
Please read about how to provide an MCVE (a Minimal, Complete, and Verifiable Example).
Not doing these things:
makes it far less likely that you'll get the help you need.
makes your question and its answers less valuable to future readers.
Here's a cleaned-up version of your code:
# Helper function to determine a string's element index in an array.
# SYNOPSIS
# indexOf needle "${haystack[#]}"
# *Via stdout*, returns the zero-based index of a string element in an array of strings or -1, if not found.
# The *return code* indicates if the element was found or not.
indexOf() {
local e ndx=-1
for e in "${#:2}"; do (( ++ndx )); [[ "$e" == "$1" ]] && echo $ndx && return 0; done
echo '-1'; return 1
}
# Define array of profile IDs, and parallel ID of profile names.
# Note: in bash 4+, this could be handled more elegantly with a single
# associative array.
profileIds=( F2CC78D2-A63F-45CB-AE7D-BF2221D41218 1C94DAD1-5FC7-46CE-9E09-576841C15093
A0E5B977-F0AF-44C9-8001-DA0511B702B8 5E9DE5BF-34E4-4A7F-AA29-461FB0631943
9AE91C88-D1B2-4227-9E95-80F492DCAA11 00000000-0000-0000-A000-4A414D460003
5E85BBF0-3483-4C80-A1FC-70AF20F82E7C E433D546-5502-4C3F-9E5F-4732ED1F0032
5C2AE16B-D4E9-4D15-B190-3CD7B28779E8 2C620A13-DF1E-4F6A-A32B-9FA3149F8A56
3B44AE14-E0CE-4621-BACF-1A9C3BA4A459 396A9D84-A9CA-4575-8D09-C9F054B76AF7
E0138F02-9A15-47BD-8CA5-7D1D0985A1A6 )
profileNames=( "AD Binding" "Energy Saver"
"Finder" "FV2 Redirect"
"Login Window/Security and Privacy" "MDM Profile"
"Restrictions" "SAC SUBCA-01"
"SAC SUBCA-02" "SAC-CA-01"
"Screensaver" "Spotlight"
"Workday Corp" )
# Feeding the list of installed profile IDs via a process
# substitution (<(...)), loop over them and print their
# respective names.
while read -r line ; do
# Find the line in the array of profile IDs and
# print the corresponding name.
if ndx=$(indexOf "$line" "${profileIds[#]}"); then
echo "${profileNames[ndx]} is present"
else
echo "WARNING: Unknown profile: $line" >&2
fi
done < <(profiles -P | awk '/attribute/ {print $4}')
As for why your code didn't loop:
You have an unconditional exit 0 statement in your loop, which means that the loop is always exited after the 1st line.
Due to using <<< to feed the list of profiles, you always get at least 1 line of input, because <<< appends a trailing newline to its input. If the input is empty, you'll get one iteration with an empty line.
As for this message:
; exit;
logout
[Process completed]
It tells me two things:
You're on OSX, and you ran the script from Finder.
The script produced no output (if there had been output, it would have printed between the exit; and logout lines).
When you run a script from Finder, the shell that is used to run the script exits after the script has run - and whether its Terminal window stays open or not depends on your Terminal preferences - in your case, the window stays open, but since the shell has exited, you can't interact with it anymore.
Either way, your particular script should yield the same output, whether it is run from Terminal directly, or via Finder.

Shell-Script: How can I pull a random letter sequence from a file?

I'm writing a script which is supposed to automatically change test users passwords.
I have a dictionary file which includes all the relevant passwords I'm bound to use and I'm trying to understand how to pull some random passwords from the file in order to set the new passwords and document which test user got which password... I'm having a hard time to find the right pattern I should use, can you please assist?
Thanks in advance
If your shell supports $RANDOM (bash does) and the allowed passwords are contained, one password per line, in a file whose name is paswd you can try
new_pass=$(awk NR==$RANDOM'%'`wc -l paswd| cut -d\ -f1`'+1' paswd)
Example using a different file
for x in {1..5} ; do
> a=$(mawk NR==$RANDOM'%'`wc -l .bashrc| cut -d\ -f1`'+1' .bashrc)
> echo $x "$a"
> done
1 muvi ${1}"$num" && break
2 $1/* ; }
3 alias acse='apt-cache search'
4 xc () { export cnt=`expr $cnt + 1` ; u=$1; shift ; x $lett`printf "%4.4d" $cnt` $u -1 $* ; }
5 alias logout='xfce4-session-logout --logout'
%
The following approach will work for a modestly sized password dictionary.
read the passwords into a bash array
start a while loop
prompt for the user's login-id
read the id
use $RANDOM to generate an index into the array
pull out the password at that position (you'll have to use some mod arithmetic)
use passwd command to change the password (check the man page)
log the user and either the number or the passwd as you desire
back to the start of the loop
You can exit the loop based on something like an empty name being entered.
I hope that's of some help.
06.11.2014
I've editted this answer with a complete, if somewhat unspectacular solution. This is for anyone who, like me, has difficulty understanding the brevity of the preferred solution. Points to note:
In bash you have to declare an array with declare -a YourArray
You should also specify a dedicated index variable: arrIdx=0 for instance
Bash arrays are dynamic so you don't need to declare its size.
Here's the whole thing:
#!/bin/bash
PRG=`basename $0`
if [ $# -lt 2 ]; then
echo "usage $PRG password_file change_log"
exit -1
fi
INFILE=$1
LOGFILE=$2
declare -a passwdArr
arrIdx=0
newPasswd=
for inputPasswd in `cat $INFILE`; do
passwdArr[$arrIdx]=$inputPasswd
echo "inputPasswd = $inputPasswd, arrIdx = $arrIdx, passwdArr[arrIdx] = ${passwdArr[$arrIdx]}"
((arrIdx += 1)) # double parentheses ensure arithmetic evaluation
done
arrSize=$arrIdx # one more than the mximum index
USER="xxx"
while [ "$USER" != "" ]; do
echo "enter user ID:"
read USER
if [ "$USER" != "" ]; then # Do nothing in this case
arrIdx=`expr $RANDOM % $arrSize`
echo "arrIdx = $arrIdx"
newPasswd=${passwdArr[$arrIdx]}
echo "$arrIdx $USER $newPasswd" >> $LOGFILE
echo -e "$newPasswd\n$newPasswd" | passwd $USER
fi
done
Important... the test if [ "$USER" != "" ] within the loop is required in order to avoid inadvertently changing the password of the administrator running the script!

Bash Shell Scripting - detect the Enter key

I need to compare my input with Enter/Return key...
read -n1 key
if [ $key == "\n" ]
echo "###"
fi
But this is not working.. What is wrong with this code
Several issues with the posted code. Inline comments detail what to fix:
#!/bin/bash
# ^^ Bash, not sh, must be used for read options
read -s -n 1 key # -s: do not echo input character. -n 1: read only 1 character (separate with space)
# double brackets to test, single equals sign, empty string for just 'enter' in this case...
# if [[ ... ]] is followed by semicolon and 'then' keyword
if [[ $key = "" ]]; then
echo 'You pressed enter!'
else
echo "You pressed '$key'"
fi
Also it is good idea to define empty $IFS (internal field separator) before making comparisons, because otherwise you can end up with " " and "\n" being equal.
So the code should look like this:
# for distinguishing " ", "\t" from "\n"
IFS=
read -n 1 key
if [ "$key" = "" ]; then
echo "This was really Enter, not space, tab or something else"
fi
I'm adding below code just for reference if someone will want to use such solution containing countdown loop.
IFS=''
echo -e "Press [ENTER] to start Configuration..."
for (( i=10; i>0; i--)); do
printf "\rStarting in $i seconds..."
read -s -N 1 -t 1 key
if [ "$key" = $'\e' ]; then
echo -e "\n [ESC] Pressed"
break
elif [ "$key" == $'\x0a' ] ;then
echo -e "\n [Enter] Pressed"
break
fi
done
read reads a line from standard input, up to but not including the new line at the end of the line. -n specifies the maximum number of characters, forcing read to return early if you reach that number of characters. It will still end earlier however, when the Return key is pressed. In this case, its returning an empty string - everything up to but not including the Return key.
You need to compare against the empty string to tell if the user immediately pressed Return.
read -n1 KEY
if [[ "$KEY" == "" ]]
then
echo "###";
fi
None of these conditions worked for me and so I've came up with this one:
${key} = $'\0A'
Tested on CentOS with Bash 4.2.46.

Resources