How to remember user input in a shell script? - bash

I have user settings in my shell script. How can I ask user for input and remember it? In example i have in code:
ALTERNATEMEMORY="false"
I need to ask user for input (when running script in 'configure' mode):
echo "Use alternate memory?"
select yn in "true" "false"; do
case $yn in
Yes ) [permament save ALTERNATEMEMORY as "true"];;
No ) [permament save ALTERNATEMEMORY as "false"];;
esac
done
Script should ask user as above, read his input, and depending of choice set ALTERNATEMEMORY to corresponding state. Next time when running script (not configuring, just running) it should remember that setting. What should I put in these square brackets?
If I'm making mistakes when posting question, please forgive me - this is first time when I'm using stackoverflow.

The standard way to remember a setting from one run to the next, even after reboots, is to use a configuration file. For system-wide settings, these configuration files are usually in /etc and could be named, for example, /etc/myprog.conf. For user-specific settings, the file is usually in the home directory with a name that starts with ., such as $HOME/.myprog.conf. Pick one of those paths and create the configuration file:
echo "ALTERNATEMEMORY=false" >path/myprog.conf
Now, have the configuration section of your script update that file as desired:
echo "Use alternate memory?"
select yn in "true" "false"; do
case "$yn" in
true)
sed -i '/ALTERNATEMEMORY/ s/.*/ALTERNATEMEMORY=true/' path/myprog.conf
break
;;
false)
sed -i '/ALTERNATEMEMORY/ s/.*/ALTERNATEMEMORY=false/' path/myprog.conf
break
;;
esac
done
In the above, we used sed -i to update the file in-place. This works on linux. If you are on a BSD system (OSX), then you will need to add two quotes to that command like sed -i "" ...
Doing substitutions with an arbitrary string
If we want to set ALTERNATEMEMORY to have the value $sel where sel is a shell variable containing arbitrary characters, then we need to escape them before doing the substitution. This can be done as follows (assuming the shell is bash):
escaped_sel=$(sed 's/[&/\]/\\&/g' <<< "$sel")
sed -i "/ALTERNATEMEMORY/ s/.*/ALTERNATEMEMORY=$escaped_sel/" path/myprog.conf

Have a mem.conf file with the below lines
UNSET
TRUE
First line can be SET/UNSET which is to check if user has already configured and second line can be TRUE/FALSE which is to check whether to use alternate memory.
Initially the first line should be set to UNSET.
Then write the script configure.sh like below :
is_set=`head -n1 mem.conf`
if [[ $is_set == "SET" ]]
then
{
echo "Your have already configured the settings."
}
else
{
echo "Use Alternate memory?"
options=("TRUE" "FALSE" "QUIT")
select opt in "${options[#]}"
do
case $opt in
"TRUE")
echo "SET" > mem.conf
echo "TRUE" >> mem.conf
echo "Configuration Saved !!"
break;
;;
"FALSE")
echo "SET" > mem.conf
echo "FALSE" >> mem.conf
echo "Configuration Saved !!"
break;
;;
"QUIT")
echo "Seems like you have not made up your mind yet !"
echo "Please come back later."
break;
;;
*) echo "invalid option"
;;
esac
done
}
fi
echo "Configuration File : "`pwd`"/mem.conf"
The advantage here is that you could force edit mem.conf to start from scratch.

Related

bash: don't prompt the select case statement from a script - possible?

I have a script which I usually run by hand. It prints some variables and asks me twice if those are correct:
echo "Is this correct?"
select yn in "Yes" "No"; do
case $yn in
Yes ) break;;
No ) exit;;
esac
done
Works perfectly.
Now however, I have been asked to automate the task completely - in other words, it shouldn't prompt me anymore and just run through.
However, I would like to retain the prompting for the normal case when I run it manually.
Is it possible to set a flag to bash or something which would just skip over the select statement? I know it's kinda silly but was wanting to see if I have an easy way out without having to completely remove that section or having to write a second script without that.
You can use test -t fd to tell if the FD is connected to a terminal or not. When the test succeeds, prompt for input, otherwise skip it.
if [ -t 0 ]; then
echo "Is this correct?"
select yn in "Yes" "No"; do
case $yn in
Yes ) break;;
No ) exit;;
esac
done
fi

Bash read command - how to accept control characters

I am using the following to read a bunch of parameters from the user.
read -p "Name`echo $'\n> '`" NAME
All my parameters have default values. I want to provide an option to the user to skip providing values at any point. i.e. user might provide values for first 3 parameters and press Ctrl+s to skip entering values for the rest of them.
How can I trap Ctrl+s?
Ctrl+S is the terminal scroll lock character, and is not immediately available. You have two options:
Work with the system, use the system standard key combos, and make life easier for yourself and everyone else.
Fight the system, insist on using this key combo, do anything it takes to make it happen, and then live with the consequences.
If you want to work with the system, consider using a blank line and/or Ctrl+D, which is already extensively used to end input. This is easy and robust:
if read -p "Name (or blank for done)"$'\n> ' name && [[ $name ]]
then
echo "User entered $name"
else
echo "User is done entering things"
fi
Alternatively, here's a start for fighting the system:
#!/bin/bash
settings=$(stty -g)
echo "Enter name or ctrl+s to stop:"
stty stop undef # or 'stty raw' to read other control chars
str=""
while IFS= read -r -n 1 c && [[ $c ]]
do
[[ $c = $'\x13' ]] && echo "Ctrl+S pressed" && break
str+="$c"
done
echo "Input was $str"
stty "$settings"
This will correctly end when the user hits Ctrl+S on a standard configuration, but since it doesn't work with the system, it needs additional work to support proper line editing and less common configurations.
Not sure what you mean by trapping. The user can input Ctrl+S by typing Ctrl+V and then Ctrl+S, and your script can then do the check:
if [[ $NAME == '^S' ]]; then
...
# skip asking for more values and continue with default values
fi

passing variables to a bash script

I have a bash script "test.sh" and one parameter I want to use is --no-email.
when I run test.sh --no-email, everything works as expected and I do not receive an email status report.
However what I really want to run is "test.sh test.cnf" where the --no-email parameter is stored in the test.cnf file along with a load of other parameters. I cant for the life of me get this to work. Perhaps I am being completely stupid and not understanding?
Many thanks
echo $*|grep -se '--no-email'&>/dev/null
SEND_MAIL=`echo $?`
echo -e "DEBUG: \$*=$*"
if [ ! "$SEND_MAIL" == "0" ]; then
echo 'Mail would have been sent!'
else
echo 'NO MAIL WOULD HAVE BEEN SENT!'
fi
If you cannot modify the script test.sh to support this you still can use this syntax to fetch parameters from a config file:
test.sh $(<test.cnf)
If this assumption is not true, i.e. you want to modify test.sh itself to support this then you have to be more specific about what happens inside test.sh.
Edit: Now the content of test.sh has been added to the question. Starting from there the most simple thing to do would be like this:
grep -sqe '--no-email' "$*"
SEND_MAIL=$?
But you wrote that you have a bunch of other paramaters. Doing a grep for each one might be inconvenient. In this case you can loop over the word of a cnf file like this:
#!/bin/bash
while read line; do
for word in $line; do
echo "examing $word"
case "$word" in
--no-email)
SEND_MAIL=0
;;
--no-foo)
NO_FOO=0
;;
*)
echo 1>&2 "WARNING: Unknown parameter: $word"
;;
esac
done
done < "$1"

Can't match any option in bash select statement

I'm trying to use the bash select statement for a command loop. The variable in the select statement is always blank. Here is a simple script that illustrates the problem:
#!/bin/bash
select term in one two exit
do
echo you selected $term
case $term in
one ) echo one; break;;
two ) echo two; break;;
exit ) echo will exit; return;;
esac
done
Here is what happens when I run this script:
$ ./test.sh
1) one
2) two
3) exit
#? one
you selected
#? two
you selected
#? exit
you selected
#? ^D
Anyone know what I might be doing wrong? I'm on Mac OS X 10.7.3. /bin/bash --version shows: GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin11)
The script works if you type in "1" or "2" rather than "one" or "two".
#jedwards gave you the immediate answer. However, if you want to protect yourself from other users having the same error, you could do something like this
select term in first second exit; do
[[ -z $term ]] && casevar=$REPLY || casevar=$term. # or, shorter, casevar=${term:-$REPLY}
case $casevar in
1|first) echo "the first option"; break ;;
2|second) echo "option no. 2"; break ;;
3|exit) echo bye; break ;;
esac
done
Note this from the bash manual:
a line is read from the standard input. If the line consists of a
number corresponding to one of the displayed words, then the value of
name is set to that word. If the line is empty, the words and prompt
are displayed again. If EOF is read, the select command completes. Any
other value read causes name to be set to null. The line read is saved
in the variable REPLY.

Multiple compound comparison in bash

I have a bash script which is getting options (using getopts). Let say they are '-n' , '-d' , '-u' . I only want to have one of the option being chosen, if not, it will prompt the user error.
The code is like this:
while getopts ":dun" name; do
case $name in
d )
DELETE='YES'
;;
u )
UPDATE='YES'
;;
n )
NEW='YES'
;;
esac
done
I can only have $DELETE or $UPDATE or $NEW being 'YES' in one time, meaning the user cannot specific '-n' and '-d' in the same time or '-u' and '-n' in the same time, how do I achieve that in a IF statement ?
I have been looking for this in stackoverflow, but can't find any. Thanks for the help, mate!
You can increment a counter every time getopts() senses one of the valid commandline options. Then, after the loop test the counter's value. If it is greater than one, then multiple options were specified.
This is a complete hack, and depends on the option variables being either unset or "YES" (and in the form I've written it, is bash-only):
if [[ "$DELETE$UPDATE$NEW" == YESYES* ]]; then
echo "Please only use one of -d, -u, and -n" >&2
exit 1
fi
(If you were using the brand-x shell instead of bash, it'd be something like if [ "$DELETE$UPDATE$NEW" = "YESYES" -o "$DELETE$UPDATE$NEW" = "YESYESYES" ]; then)
You're better off setting your variables to true or false, so you can simply write
if $DELETE; then
echo "You've already specified UPDATE!"
fi
and similar.
However, options are called that because they are supposed to be optional, and usually orthogonal. (There are a few instances where options of common utilities exclude each other, but the vast majority don't). What you want is really a mandatory mode of operation, so you shouldn't receive it as an option at all, but simply as the first command-line argument.
You can use some getopt-kludge to manage having only one of the option being chosen.
#!/bin/bash
while [ $# -gt 0 ]; do
case "$1" in
-u)
echo "got -u"
break
;;
-d)
echo "got -d"
break
;;
-n)
echo "got -n"
break
;;
*)
echo "some error"
break
;;
esac
done

Resources