LinuxQuestions.org
Visit Jeremy's Blog.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 05-18-2019, 03:32 PM   #1
Johnny_Metal
Member
 
Registered: Feb 2016
Posts: 72

Rep: Reputation: 1
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
 
Old 05-19-2019, 01:57 AM   #2
ondoho
LQ Addict
 
Registered: Dec 2013
Posts: 19,872
Blog Entries: 12

Rep: Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053Reputation: 6053
you should find out what your default /bin/sh actually is:
Code:
ls -l /bin/sh
for me it links to bash.
if i want to make sure i write simpler scripts i sometimes use
Code:
#!/bin/dash
which is "more" POSIX-compliant than bash.
AFAIK, no single shell is 100% POSIX compliant, but dash comes pretty close.

then i strongly question the requirement to not use "if" statements. why on earth?
 
2 members found this post helpful.
Old 05-19-2019, 04:24 AM   #3
Johnny_Metal
Member
 
Registered: Feb 2016
Posts: 72

Original Poster
Rep: Reputation: 1
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.
 
Old 05-19-2019, 04:58 AM   #4
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
I wouldn't use those
Code:
(echo ... | grep ... )
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.
 
2 members found this post helpful.
Old 05-19-2019, 04:59 AM   #5
Johnny_Metal
Member
 
Registered: Feb 2016
Posts: 72

Original Poster
Rep: Reputation: 1
Solved it. Here is the final script.

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.
 
Old 05-19-2019, 05:22 AM   #6
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
Quote:
Originally Posted by Johnny_Metal View Post
EDIT: Applied pan64's suggestion not to use (echo ... | grep ...)
not really, I can still see ....

Quote:
Originally Posted by Johnny_Metal View Post
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 View Post
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.
 
1 members found this post helpful.
Old 05-19-2019, 06:20 AM   #7
Johnny_Metal
Member
 
Registered: Feb 2016
Posts: 72

Original Poster
Rep: Reputation: 1
Any suggestions on how to get rid of the last (echo ... | grep ...)?
 
Old 05-20-2019, 12:31 AM   #8
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
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
 
1 members found this post helpful.
Old 05-20-2019, 01:08 PM   #9
MadeInGermany
Senior Member
 
Registered: Dec 2011
Location: Simplicity
Posts: 2,780

Rep: Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198
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.
 
2 members found this post helpful.
Old 05-24-2019, 03:14 PM   #10
Johnny_Metal
Member
 
Registered: Feb 2016
Posts: 72

Original Poster
Rep: Reputation: 1
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.
 
Old 05-25-2019, 02:23 AM   #11
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
This is overcomplicated.
Code:
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.
 
Old 05-25-2019, 06:09 AM   #12
Johnny_Metal
Member
 
Registered: Feb 2016
Posts: 72

Original Poster
Rep: Reputation: 1
All that's left is to take into account the sign. Here's a few examples of how it should behave:
  • 5 -> pass
  • -5 -> pass
  • +5 -> pass
  • "" -> exit silently
  • d5 -> exit with error message
  • 5d -> exit with error message
 
Old 05-25-2019, 10:34 AM   #13
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,792

Rep: Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306Reputation: 7306
yes, and now you can try to write your own test commands. or a case-esac list if you wish.
 
Old 05-25-2019, 03:36 PM   #14
MadeInGermany
Senior Member
 
Registered: Dec 2011
Location: Simplicity
Posts: 2,780

Rep: Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198Reputation: 1198
With a case-esac (glob) it is a bit tricky.
As a function
Code:
is_num(){
  case $1 in
  ("")
    return 1
  ;;
    (?*[!0-9]*|[!-+0-9]*)
    return 2
  ;;
  esac 
  return 0
}
The function sets exit status 0,1,2, that can be queried by $? or tested for 0=success like this interactive example
Code:
until
  echo "enter a number"; read var; is_num "$var"
do
  echo "not a number"
done
The until takes the exit status of the last command before the do,
 
1 members found this post helpful.
Old 05-25-2019, 06:03 PM   #15
Johnny_Metal
Member
 
Registered: Feb 2016
Posts: 72

Original Poster
Rep: Reputation: 1
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?
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] How to deal with bash[$style] arrays in a POSIX compliant script? GrapefruiTgirl Programming 8 01-02-2015 11:17 PM
POSIX compliant distributed file systems without using shared storage anon.addon Linux - Software 1 12-12-2009 09:54 AM
POSIX compliant "exists" basak Programming 7 09-24-2007 12:17 PM
Open source, posix compliant, person renderer? Elomis Linux - Software 0 01-10-2005 04:21 PM
if statements and case statements not working in bourne shell script mparkhurs Programming 3 06-12-2004 02:41 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 06:33 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration