[SOLVED] Creating a temperature conversion POSIX compliant script without "if" statements
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
Creating a temperature conversion POSIX compliant script without "if" statements
Hello
I am trying to write a POSIX compliant shell script to convert temperatures using rofi, without "if" statements.
In the first part I prompt for scale. I want a notification to be sent in case of invalid input and a silent exit in case of empty string.
This is what I've got so far, but it's not working.
Code:
TO=$(printf "Celsius\\nFahrenheit" | rofi -dmenu -p "Convert to which scale?")
(echo "$TO" | grep -Evq '^(Celsius|Fahrenheit)$') && \
( ( (echo "$TO" | grep -Evq '^$') && notify-send "Please choose either Celsius or Fahrenheit" && exit 1) || exit 0)
Any one have suggestions?
Here is the full script. Notice that it will exit with error message on first promt even in case of empty string.
Code:
#!/bin/sh
# Determine what scale to convert to
TO=$(printf "Celsius\nFahrenheit" | rofi -dmenu -p "Convert to which scale?")
(echo "$TO" | grep -Evq '^(Celsius|Fahrenheit)$') && \
(echo "$TO" | grep -Evq '^$') && notify-send "Please choose either Celsius or Fahrenheit" && exit 1
# Determine what temperature to convert from
TEMP=$(rofi -dmenu -p "Please input integer temperature")
(echo "$TEMP" | grep -Eq '(^$|[^0-9])') && notify-send "Temperature must be an integer" && exit 1
# Compute and send notification
case "$TO" in
Fahrenheit) notify-send "$TEMP Cº is equal to $(echo "scale=2; $TEMP * 1.8 + 32" | bc) Fº";;
Celsius) notify-send "$TEMP Fº is equal to $(echo "scale=2; ($TEMP - 32) / 1.8" | bc) Cº";;
*) notify-send "Unexpected case. Trying to convert to $TO";;
esac
I had already done as you suggested and determined my default /bin/sh. I am on Ubuntu Mate 19.04 and the default is set to dash, or rather, it is a link to dash.
You can think of the requirement of no "if" statements as a personal challenge and/or style preference. I achieved the objective by using "if" statements at first, however, it seems to me that I can achieve it without using them and so I want to find out how to do it.
structures, instead, I would try test (see man test) and case/esac.
In general you need not use () (the only exception is $(command) - use {} instead, if you need.
EDIT: Applied pan64's suggestion not to use (echo ... | grep ...)
Code:
#!/bin/sh
# Determine what scale to convert to
TO=$(printf "Celsius\nFahrenheit" | rofi -dmenu -p "Convert to which scale?")
test "$TO" = "" && exit 0
test "$TO" != "Celsius" -a "$TO" != "Fahrenheit" && notify-send "Please choose either Celsius or Fahrenheit" && exit 1
# Determine what temperature to convert from
TEMP=$(rofi -dmenu -p "Please input integer temperature")
(echo "$TEMP" | grep -Eq '(^$|[^0-9])') && notify-send "Temperature must be an integer" && exit 1
# Compute and send notification
case "$TO" in
Fahrenheit) notify-send "$TEMP Cº is equal to $(echo "scale=2; $TEMP * 1.8 + 32" | bc) Fº";;
Celsius) notify-send "$TEMP Fº is equal to $(echo "scale=2; ($TEMP - 32) / 1.8" | bc) Cº";;
*) notify-send "Unexpected case. Trying to convert to $TO";;
esac
The problem had something to do with the script not exiting when encountering "exit" statements. From what I've read in other places it seems to me like it had something to do with the "exit" statements being within parentheses.
I am marking this thread as solved, but if anyone wishes to explain this last point I would appreciate it.
Also, tips that would result in simplification or improvement of speed (even though that would be hard at this point) will be appreciated.
Thank you for the help.
Last edited by Johnny_Metal; 05-19-2019 at 05:14 AM.
EDIT: Applied pan64's suggestion not to use (echo ... | grep ...)
not really, I can still see ....
Quote:
Originally Posted by Johnny_Metal
Code:
#!/bin/sh
# Determine what scale to convert to
TO=$(printf "Celsius\nFahrenheit" | rofi -dmenu -p "Convert to which scale?")
test "$TO" = "" && exit 0
test "$TO" != "Celsius" -a "$TO" != "Fahrenheit" && notify-send "Please choose either Celsius or Fahrenheit" && exit 1
# Determine what temperature to convert from
TEMP=$(rofi -dmenu -p "Please input integer temperature")
(echo "$TEMP" | grep -Eq '(^$|[^0-9])') && notify-send "Temperature must be an integer" && exit 1
# Compute and send notification
case "$TO" in
Fahrenheit) notify-send "$TEMP Cº is equal to $(echo "scale=2; $TEMP * 1.8 + 32" | bc) Fº";;
Celsius) notify-send "$TEMP Fº is equal to $(echo "scale=2; ($TEMP - 32) / 1.8" | bc) Cº";;
*) notify-send "Unexpected case. Trying to convert to $TO";;
esac
Quote:
Originally Posted by Johnny_Metal
The problem had something to do with the script not exiting when encountering "exit" statements. From what I've read in other places it seems to me like it had something to do with the "exit" statements being within parentheses.
That is exactly happened because of ( commands ) - which actually means a new shell will be used to execute those commands. And an exit [inside] will only terminate that shell, not the one you started.
grep ^$ means empty lines, you need to find the test which will check if TEMP is empty. grep [^0-9] will check if TEMP contains non-digit characters (although it is not really correct). https://stackoverflow.com/questions/...number-in-bash
The case (keyword) takes a glob as an expression to compare with.
A glob is anchored; a floating match must begin and end with * (any number of any characters).
The glob takes ! as a negation of a [character set]. Some shells support ^ as a synonym.
The case takes | as an OR.
The case takes (expression) or expression).
Code:
case $TEMP in (""|*[!0-9]*) notify-send ...; esac
Unlike [ ] or test, the case does no further expansions on $TEMP, so quotes can be omitted. (Actually the case behaves like the == in a non-Posix [[ ]].)
In a case the branch code must end with ;; but can be omitted before the esac. A ; or newline is required then.
Last edited by MadeInGermany; 05-20-2019 at 01:12 PM.
I actually changed the script a little further to account for the sign (+ or -). The grep part looks like:
Code:
(echo "$TEMP" | grep -Eq '^$') && exit || (echo "$TEMP" | grep -Evq '^[+-]?[0-9]+$') && notify-send "Temperature must be an integer" && exit 1
Trying to use a case as suggested, but have not been successfull thus far...
EDIT:
Remove "+" sign if it exists. Limit minimum temperature to nearest integer values to absolute zero. Can't find a way to apply the regular expression with a "case" though...
Code:
#!/bin/sh
# Determine what scale to convert to
TO=$(printf "Celsius\\nFahrenheit" | rofi -dmenu -p "Convert to which scale?")
test "$TO" = "" || (test "$TO" != "Celsius" && test "$TO" != "Fahrenheit" && notify-send "Please choose either Celsius or Fahrenheit") && exit
# Determine what temperature to convert from
TEMP=$(rofi -dmenu -p "Please input integer temperature")
case $TEMP in [!0-9+-]*) notify-send "Temperature must be an integer" && exit 1; esac
# Remove "+" sign if it is present
oldIFS=$IFS && IFS=+ && case $TEMP in +*) set -- $TEMP && TEMP=$2; esac; IFS=$oldIFS
# Compute and send notification
case "$TO" in
Fahrenheit) (test "$TEMP" -lt 273 && notify-send "Temperature in Celsius must be at least -273º" && exit) || notify-send -- "$TEMP Cº is equal to $(echo "scale=2; $TEMP * 1.8 + 32" | bc) Fº";;
Celsius) (test "$TEMP" -lt 459 && notify-send "Temperature in Fahrenheit must be at least -459º") || notify-send -- "$TEMP Fº is equal to $(echo "scale=2; ($TEMP - 32) / 1.8" | bc) Cº";;
*) notify-send "Unexpected case. Trying to convert to $TO";;
esac
Last edited by Johnny_Metal; 05-25-2019 at 05:05 AM.
case $TEMP in
"" ) <do something if TEMP is empty> ;;
*[!0-9]*) ) <do something if TEMP contains non-number chars> ;;
esac
# do something is some like this:
notify-send -- <message> && exit 1
# without any ( )
don't use () but {}.
add set -xv at the beginning of your script to see what's happening.
Managed to finally do the check using case/esac. Also added "-no-custom" option to rofi, which cuts need for check of $TO.
Code:
#!/bin/sh
MIN_CELSIUS=-237
MIN_FAHR=-459
is_num(){
case $1 in
("") return 1;;
(?*[!0-9]*|[!-+0-9]*) return 2;;
esac
return 0
}
# Determine what scale to convert to
TO=$(printf "Celsius\\nFahrenheit" | rofi -dmenu -no-custom -p "Convert to which scale?")
until
TEMP=$(rofi -dmenu -p "Please input integer temperature"); is_num "$TEMP"
do
result=$?
test "$result" = 1 || (test "$result" = 2 && notify-send "Temperature must be an integer") && exit
done
# Remove "+" sign if it is present
oldIFS=$IFS && IFS=+ && case $TEMP in +*) set -- "$TEMP" && TEMP=$2; esac; IFS=$oldIFS
# Compute and send notification
case "$TO" in
Fahrenheit) (test "$TEMP" -lt "$MIN_CELSIUS" && notify-send "Temperature in Celsius must be at least $MIN_CELSIUSº" && exit) || notify-send -- "$TEMP Cº is equal to $(echo "scale=2; $TEMP * 1.8 + 32" | bc) Fº";;
Celsius) (test "$TEMP" -lt "$MIN_FAHR" && notify-send "Temperature in Fahrenheit must be at least $MIN_FAHRº") || notify-send -- "$TEMP Fº is equal to $(echo "scale=2; ($TEMP - 32) / 1.8" | bc) Cº";;
*) notify-send "Unexpected case. Trying to convert to $TO";;
esac
There is just one more thing that I noticed. rofi is exhibiting a behaviour I do not fully understand. If I type a dash followed by any character that is not part of one of rofi's options it ignores it and accepts whatever comes after it.
For example, if I type "-i", it is recognized as invalid input and does not allow me to proceed. If I type something like "-5" it ignores it and minds only what I type after. Anyone know why?
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.