I'm creating a script to update my linux distribution if I need to wipe the HD or I need to install Linux on another machine. So this script basically install all the programs I usually need. At the beginning a have a "read" command that asks if I want to install all the packages automatically or not. If I choose not, for each program not found it should ask me I want it to be installed and I use this code
if [[ $installall == "yes" ]]; then
echo " Installing $sciprog..."
sudo apt-get install -y $sciprog >/dev/null
{
scitest=`dpkg -s $sciprog | grep Status`
} 2>${HOME}/musthave.errorlog
if [[ $scitest != "Status: install ok installed" ]]; then
echo " I've encountered problems installing $sciprog that I can't resolve. "
echo " Consider installing $sciprog manually. "
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.notinstalled
else
echo " $sciprog installed correctly!"
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.installed
fi
else
echo " Seems like $sciprog is not installed... Do you want to download it?"
echo " Type 'y' for yes."
read secondyn ### THIS IS THE GUILTY COMMAND ###
if [[ $secondyn == "y" ]]; then
echo " Installing $sciprog ..."
sudo apt-get install -y $sciprog >/dev/null
{
checkinstall=`dpkg -s $sciprog | grep Status`
} 2>>${HOME}/musthave.errorlog
if [[ $checkinstall != "Status: install ok installed" ]]; then
echo " I've encountered problems installing $sciprog that I can't resolve. "
echo " Consider installing $sciprog manually. "
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.notinstalled
else
echo " $sciprog installed correctly!"
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.installed
fi
else
echo " Skipping $sciprog ..."
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.notinstalled
fi
### some more code which works as expected. All the code above is inside a
### while...do...done loop which reads line by line the file at the end
done <${HOME}/file.list
But if I run the script, it skips the "read" command in the else clause and assumes it to be "n"...
I can't figure out why, there are other read function also inside if...then...else...fi loops and they work as expected...
Any ideas?
The relevant portions of the code are still not complete but based on the comments I'm going to guess that your while loop looks like
while read -r ... ; do
# do stuff ...
# read user input
read -r var
done < file
From this the problem is immediately apparent: the inner read is getting its input from the same place as the outer loop, namely stdin which has been redirected from file, and not the user. For a slightly more portable alternative that does not depend on kernel-level support for /dev/tty, just use a different file descriptor other than stdin for the while loop.
while read -r ... <&9; do
# loop stuff
# stdin still attached to the terminal untouched,
# so this reads from the terminal as expected
read -r var
done 9< file
Notice that this example uses fd 9 for the file, leaving fd 0 (stdin) alone. Take a look at the BashFAQ 089 for more details.
Try reading from the controlling terminal device:
read secondyn </dev/tty
read secondyn < /proc/${PPID}/fd/0
That will look to the parent's input, which should still be stdin.
Related
Currently trying to write bash that will do the following.
check if curl is installed
print out "which curl" before running it so that the user is able to opt in/out of running something they consider unsafe.
Use case is when you download a big script from github and you want to have more control over what it is doing. Also to be more aware of how it works.
I am not sure how to include this opt in/out code without messing up the "return" echo. Maybe the answer is to use something different that the read -n 1 -s -r -p code. I like that solution because it allows hitting any key to continue.
To be clear. If I check for YES/NO later on, it is messed up because it will contain the character used to continue by pressing any key. In my output example the space bar was hit to continue
#! /bin/bash
# Returns YES if installed otherwise return NO
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press any key to approve running it"
read -n 1 -s -r -p "which curl"
echo ""
if which curl > /dev/null; then
echo "YES"
else
echo "NO"
fi
}
main() {
RESULT=$(check_curl_installed)
echo $RESULT
echo x${RESULT}x
}
main "$#"
exit 0
This is the output
user#computer:tmp$ ./check_curl_installed.sh
Before running, the command it will be printed below.
Press any key to approve running it
which curlYES
x YESx
Instead of using the output of the function, use its exit status.
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press any key to approve running it"
read -n 1 -s -r -p "which curl"
echo ""
if which curl > /dev/null; then
return 0
else
return 1
fi
}
if check_curl_installed
then
# do something
else
# do something else
fi
how about this modified version to get only the Y key to answer...
#! /bin/bash
# Returns YES if installed otherwise return NO
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press Y key to approve running it"
read -n 1 -r -s -p "Which curl?"
if [[ $REPLY =~ ^[Yy]$ ]]; then
if which curl > /dev/null ; then
printf "\nYES It is installed\n"
else
printf "\nNO It is not installed\n"
fi
else
printf "\nExiting - not checking\n"
fi
}
main() {
check_curl_installed
}
main "$#"
exit 0
Your echo result is, i think just pulling the first line of the check_curl_installed function...
Maybe if result was set to an array?
my testing around has shown that it's forgetting variables in the function at the higher main function. I even tried exporting to get the main function to work, but to no avail. I'm not super strong in bash, so i apologize on that.
Also might work better to put the echos inside each function, instead of shoving them into a variable...
Most, if not all, languages, only return one value from a function. Maybe this is why your output don't do as you want? a quick search brought this up https://unix.stackexchange.com/questions/408543/how-can-a-bash-function-return-multiple-values
when I run this script that I wrote to help installing AUR packages:
enter #!/bin/bash
#bash
function GO() {
pack="$1"
cower -ddf $pack
cd "/home/$USER/applications/$pack"
expect -c " set timeout -1
eval spawn makepkg -Ascfi --noconfirm
expect -nocase \"password for $USER:\" {send \"$pass\r\"}
interact;"
cd "../"
}
package="$1"
echo "I need your password for this, can I have it please?"
read -s pass
cd "/home/$USER/applications"
if [ "$package" == "update" ]
then
file="/home/$USER/applications/update.pkgs"
cower -u > file
while IFS= read -r line
do
package=$(echo $line | cut -d " " -f2)
GO $package
done <"$file"
else
GO $package
fi
echo "have a good day."
exit 0
sometimes interact just stoppes after it enters the password and it just echos "have a good day." and exits. am I doing something wrong? timeout is < 0, I have interact aftet the expect statement, anything I am missing?
The only thing I can see is that the password might have a quote in it. You might want to do this:
env _user="$USER" _pass="$pass" expect <<'END'
set timeout -1
spawn makepkg -Ascfi --noconfirm
expect -nocase "password for $env(_user):" {
send -- $env(_pass)
send "\r"
}
interact
END
No need to eval spawn here.
Using the quoted heredoc makes the code easier to read too.
I recently created a different thread with an issue concerning a for loop in a bash script I was writing for my GCSE coursework. I have another issue with the same bash script (however it has evolved a fair bit since last time).
Here is the code:
#!/bin/bash
# A script that creates users.
uerror='^[0-9]+$'
echo "This is a script to create new users on this system."
echo "How many users do you want to add? (in integer numbers)"
read am
echo " "
if [[ $am =~ $uerror ]] ; then
echo "ERROR: Please use integer numbers."
echo "Please re-enter the amount."
read am ;
else
echo " "
for i in $(seq "$am")
do
echo "Enter a username below:"
read usern
sudo useradd $usern
sudo passwd $usern
echo " "
echo "User $i '$usern' added."
echo " "
echo "What group do you want to add $usern to?"
read group
sudo usermod $usern -aG $group
echo "$usern added to $group"
echo " "
echo "-------------------"
echo " "
done
fi
The issue is in the if statement. It's purpose is to stop users entering anything other than an integer number. But for some reason, I don't seem to be able to capture the input from the read am part. Instead the script skips straight onto the for loop where the $(seq "$am") obviously will have issues comprehending an input that is not a number.
The output from this error is as follows.
seq: invalid floating point argument
However, I don't think this is relevant because as far as I can tell, the issue is with the if / else statement.
If anyone could point me in the right direction of what I need to do to fix this, I would be greatly appreciative.
I'd also like to iterate that I am still learning how to write bash scripts (and not in a particularly organised manner) so I've probably made a very simple mistake. Apologies for that.
Thanks,
Callum.
EDIT: I mistyped an echo message, I've now changed that so it actually makes sense.
If you want to read in a number and make sure it is a number use a while loop:
while read -p 'type a number:' n ; do
# Exit the loop if the input is a number
[[ "$n" =~ ^[0-9]+$ ]] && break
echo "This was not a number! Don't trick me!"
done
# Now can use `seq`
seq "$n"
The if statement in your example would do the completely the wrong thing. It checks if the input is a number and in that case asks for the input again and exits the script. If you don't type a number, it uses the (wrong) input in the else branch.
Replace your whole file with this:
#!/bin/bash
# A script that creates users.
uerror='^[0-9]+$'
echo "This is a script to create new users on this system."
echo "How many users do you want to add? (in integer numbers)"
read am
echo " "
while true; do
if [[ $am =~ $uerror ]] ; then
break;
else
echo "Must be integer"
echo "Please re-enter: "
read am ;
fi
done
for i in $(seq "$am")
do
echo "Enter a username below:"
read usern
sudo useradd $usern
sudo passwd $usern
echo " "
echo "User $i '$usern' added."
echo " "
echo "What group do you want to add $usern to?"
read group
sudo usermod $usern -aG $group
echo "$usern added to $group"
echo " "
echo "-------------------"
echo " "
done
I am writing a wrapper script around mail. There is a function in the program that I need for looping back to the main menu, but just before the function is declared, the program just exits back to the main prompt. Here is the code:
function restart ()
{
m
}
clear
echo Working...
echo If you are prompted for your sudo password or asked if you want to continue, then you are being
echo prompted to install mailutils. This is normal upon first-time use, or
echo use on a computer without mailutils installed.
echo
echo Starting in 5 seconds...
sleep 5
echo Examining dependencies...
dpkg -l | grep -qw mailutils || sudo apt-get install mailutils
echo Starting client...
function m ()
{
clear
echo Welcome to the Terminal GMail Client, or TGC!
echo Please enter your gmail address:
read acc
name=${acc%#*}
echo Welcome, $name! Would you like to read[R] or write[W] emails?
read opt
if [ $opt=="R" ] || [ $opt=="r" ]
then
echo Working...
sleep 1
clear
mail -u $acc -p
restart
elif [ $opt=="W" ] || [ $opt=="w" ]
then
clear
echo Working...
sleep 1
clear
echo Enter the subject here:
read sub
echo Enter the recipients address here:
read rec
echo Enter carbon copy [CC] here or leave blank for none:
read cc
echo Enter blind carbon copy [Bcc] here or leave blank for none:
read bcc
echo Enter the body of the email here:
read body
echo Sending to $rec...
mail -s $sub -c $cc -b $bcc --user=$acc $rec "$body"
echo Done! Going to main menu in 2 seconds...
sleep 2
restart
fi
}
You see, there is no error, and I am put back at the prompt right after line 15, after 'Starting Client...'.
As others have pointed out in the comments: there's no need for multiple shell functions and recursion - a simple while loop will do.
The following is a revised version of your code with proper quoting and rudimentary error handling. Your script will need a lot more input validation and error checking to stand the test of real-world use.
But perhaps this will get you started.
#!/usr/bin/env bash
clear
echo 'Working...'
echo 'If you are prompted for your sudo password or asked if you want to continue, then you are being
prompted to install mailutils. This is normal upon first-time use, or
use on a computer without mailutils installed.'
echo 'Starting in 5 seconds...'
sleep 5
echo 'Examining dependencies...'
dpkg -l | grep -qw mailutils || sudo apt-get install mailutils || exit
clear
echo 'Starting client...'
while true; do
echo 'Welcome to the Terminal GMail Client, or TGC!'
echo 'Please enter your gmail address:'
read -r acc
name=${acc%#*}
echo "Welcome, $name! Would you like to read[R] or write[W] emails or quit[Q]?"
read -r opt
case $opt in
r|R)
echo 'Working...'
sleep 1
clear
mail -u "$acc" -p || { echo "ERROR: Please try again." >&2; continue; }
;;
w|W)
clear
echo 'Working...'
sleep 1
clear
echo 'Enter the subject here:'
read -r sub
echo "Enter the recipient's address here:"
read -r rec
echo 'Enter carbon copy [CC] here or leave blank for none:'
read -r cc
echo 'Enter blind carbon copy [Bcc] here or leave blank for none:'
read bcc
echo 'Enter the body of the email here:'
read -r body
echo "Sending to $rec..."
mail -s "$sub" -c "$cc" -b "$bcc" --user="$acc" "$rec" "$body" || { echo "ERROR: Please try again." >&2; continue; }
echo 'Done! Going to main menu in 2 seconds...'
sleep 2
;;
q|Q)
echo 'Goodbye.'
exit 0
;;
*)
echo 'ERROR: Unknown command. Please try again.' >&2
;;
esac
done
This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
ssh invocation in script function
Below UNIX script abruptly ends while reading second line from file. When I comment 'ssh' command the script works as expected. I think I will have to run ssh command in a different process, but haven't got a handle yet as regards to how to do that. Any help in resolving this problem is highly appreciated.
*#!/usr/bin/ksh
exec 3<&0
exec 0<./bulkInput.dat
#cat ./bulkInput.dat | while read searchToken || (echo "reading failedi $?" && false)
index=0
while true
do
index=`expr $index + 1`
if [ $index -gt 450 ]
then
echo "Index limit reached. Now exiting"
exit 0
fi
read searchToken
if [ $? -ne "0" ]
then
echo "Read failed"
fi
echo "Search token is "${searchToken}
echo "************************************ **********************************"
echo "plsa0666 WSIP server " > WSIP.log
ssh zq742888#plsa0666 'grep -r '$searchToken' /logs/jas/was60/wsip/wsip*/wsip*' >> WSIP.log
echo "plsa0667 WSIP server " >> WSIP.log
#ssh zq742888#plsa0667 'grep -r '$searchToken' /logs/jas/was60/wsip/wsip*/wsip*' >> WSIP.log
echo "plsa0668 WSIP server " >> WSIP.log
#ssh zq742888#plsa0668 'grep -r '$searchToken' /logs/jas/was60/wsip/wsip*/wsip*' >> WSIP.log
echo "plsa4407 WSIP server " >> WSIP.log
#ssh zq742888#plsa4407 'grep -r '$searchToken' /logs/jas/was60/wsip/wsip*/wsip*' >> WSIP.log
echo "plsa0412 server " >> WSIP.log
cp WSIP.log bulk/WSIP.log_${searchToken}
echo $?
done
exec 0<&3
echo "Exiting script"*
ssh(1) is reading all of stdin and exhausting it, causing the next shell read to return false and break the loop. Try one of these:
ssh -n zq742888#plsa0666 ...
or
ssh < /dev/null zq742888#plsa0666 ...
to prevent this behavior.
Run the ssh command from the shell prompt and see what it does. If it is asking for input (e.g. password) then that may be problem.
There is also a flag to run in script mode(from memory -b but you should check) and that may also help you.
The -i flag allows you to specify the key to use if that is the problem.