<h1>Before the start of developing
<p dir="auto">This is my first tutorial about programming ever. I will be trying to explain everything as simple as possible, step-by-step. If are you software engineer or total beginner I hope it will be interesting and valuable article for you. As the title suggests we will be creating Live Weather application in bash. Why? I have started IT engineering studies last year and my exercise was to build similiar application using bash so I find it is a good idea to show you my work and teach you something new.
<h1>First step - application concept
<p dir="auto">The goal is to build an app that shows current weather for chosen city. There are instructions our final product will be doing, which we have to implement:
<ol>
<li><strong>Executing with arguments- app user has to give some details like location (otherwise app will use default location), if dynamic updates are allowed (true or false) and expected degrees (Fahrenheit or Celsius).
<li><strong>Downloading data- most important part of app. Before data are shown there is a need to download it and then display on the screen. We will use OpenWeatherMap API to receiving information about weather every 10 minutes.
<li><strong>Saving data to file- we would not like download data every second when it is not necessary, so we will use file to keep receceived records.
<li><strong>Displaying weather- last thing our app have to do is to show weather, with easy-to-understand visual effects.
<h1>What do we need?
<p dir="auto">Things we need to start developing:
<h4>Linux bash shell
<p dir="auto">If your operating system is Windows you have to install it. <a href="https://www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-on-windows-10/" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">How?. Personally I prefer working with Linux installed on VPS Server I am paying about $5 monthly.
<h4>OpenWeatherMap API key
<p dir="auto">API key is a password which gives access to OpenWeatherMap API. You can <a href="https://home.openweathermap.org/users/sign_up" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">create account on OpenWeatherMap here. After account is created go to <a href="https://home.openweathermap.org/" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Home -> <strong>API keys tab. Now you should see Dasboard tab with your API Key.<br />
<img src="https://images.hive.blog/768x0/https://steemitimages.com/DQmedz2dKNaFqHbLdno1E7ETYCB8nBibpchKTNqBRNLU9sC/owmapi.png" alt="owmapi.png" srcset="https://images.hive.blog/768x0/https://steemitimages.com/DQmedz2dKNaFqHbLdno1E7ETYCB8nBibpchKTNqBRNLU9sC/owmapi.png 1x, https://images.hive.blog/1536x0/https://steemitimages.com/DQmedz2dKNaFqHbLdno1E7ETYCB8nBibpchKTNqBRNLU9sC/owmapi.png 2x" /><br />
As you can read: <strong>Activation of an API key for Free and Startup accounts takes 10 minutes.
<h4>Jq (to parse JSON data)
<p dir="auto">Data downloaded from OpenWeatherMap must be parsed to extract records we need. Jq is very helpful here. If you have not installed Jq open Linux bash shell and use following command:
<pre><code>sudo apt-get install jq
<h4>Notepad++
<p dir="auto">It will help us while writing code by making it more readable. <a href="https://notepad-plus-plus.org/" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Download here.
<h4>Everything done?
<p dir="auto">Now you can ask "why do I need Notepad++?" The answer is: we need source code editor to create and then run our app on PC or VPS. OK. Let's code!
<h1>Notepad preparing
<ol>
<li>Open <strong>Notepad++ and create new project (File -> New).
<li>Use <strong>Edit tab to change EOL (End of Line encoding) to Unix. It is vital to avoid some errors later while executing.<br />
<img src="https://images.hive.blog/768x0/https://steemitimages.com/DQmNezZVj1yoLJFcDwNcU6uyEdS8ypkLMWHzbWSRWbn42Vp/unixntpd.png" alt="unixntpd.png" srcset="https://images.hive.blog/768x0/https://steemitimages.com/DQmNezZVj1yoLJFcDwNcU6uyEdS8ypkLMWHzbWSRWbn42Vp/unixntpd.png 1x, https://images.hive.blog/1536x0/https://steemitimages.com/DQmNezZVj1yoLJFcDwNcU6uyEdS8ypkLMWHzbWSRWbn42Vp/unixntpd.png 2x" />
<li>Change Language to <strong>Shell (Language tab -> S -> Shell).
<h1>Coding
<p dir="auto">Now, according to <strong>aplication concept, first step is <strong>Executing with arguments. In order to make it more clear look:<br />
<img src="https://images.hive.blog/768x0/https://steemitimages.com/DQmVUTa1iK6zc6hRhSsJu7Tg4hZmaH7hyZKGo57quzVUMth/DQmTb6Cwk5FqfZisJBLzLBYFfXgJ6xgQ2K33ns7PeiymsKR_1680x8400.png" alt="DQmTb6Cwk5FqfZisJBLzLBYFfXgJ6xgQ2K33ns7PeiymsKR_1680x8400.png" srcset="https://images.hive.blog/768x0/https://steemitimages.com/DQmVUTa1iK6zc6hRhSsJu7Tg4hZmaH7hyZKGo57quzVUMth/DQmTb6Cwk5FqfZisJBLzLBYFfXgJ6xgQ2K33ns7PeiymsKR_1680x8400.png 1x, https://images.hive.blog/1536x0/https://steemitimages.com/DQmVUTa1iK6zc6hRhSsJu7Tg4hZmaH7hyZKGo57quzVUMth/DQmTb6Cwk5FqfZisJBLzLBYFfXgJ6xgQ2K33ns7PeiymsKR_1680x8400.png 2x" /><br />
Don't worry, It is easy to implement.
<h4>First lines
<p dir="auto">Let's write some code in already created notepad++ file:
<pre><code>#!/bin/bash
apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
defaultLocation="Warsaw"
dynamicUpdates=0
degreeCharacter="c"
data=0
lastUpdateTime=0
<p dir="auto"><em>Explanation: first line is called <strong>a shebang tells the shell about program we want to use when executed. Then we have a few variables:<br />
<strong>apiKey- put your API Key between quotes. It is string (text) variable.<br />
<strong>defaultLocation- if there is no given location by user app will use this location.<br />
<strong>dynamicUpdates- 0 means false, 1 is true. By deafult updates were disabled.<br />
<strong>degreeCharacter- allowed values are: <strong>f (Fahrenheit) or <strong>c (Celsius). It is char (character) variable.<br />
<strong>data and <strong>lastUpdateTime are temporary empty. <strong>data variable keeps downloaded data, while <strong>lastUpdateTime holds last update time.
<h4>Options
<p dir="auto">Now we have to implement <strong>options as easy access to app settings. I have mentioned about options several times. But what exactly are they? When we want to run bash script we have to use command. It looks like:
<pre><code>. LiveWeather.sh -l Berlin -d -f
<p dir="auto">It is possible to use following commands:
<pre><code>. LiveWeather.sh -l Berlin -f -d
. LiveWeather.sh -l Berlin -f
. LiveWeather.sh -l Belgium -d
. LiveWeather.sh -l Belgium
. LiveWeather.sh
<p dir="auto">And the like...<br />
<em>Explanation: In order to execute app we use command ". NAME.sh", then it is possible to insert <strong>options.<br />
As you can see above every option is preceded by minus (<strong>-), expect word <strong>Berlin, because it is <strong>argument of option. The order of arguments doesn't matter, but <strong>argument of option always must be placed after option.
<h4>Differences at runtime
<p dir="auto">I would like to clarify differences between running app with options like:
<pre><code>. LiveWeather.sh -l Berlin -f -d
<p dir="auto">App will be displaying weather for Berlin with dynamic updates and Fahrenheit degrees. It means default setting will be replaced with:
<pre><code>defaultLocation="Berlin"
dynamicUpdates=1
degreeCharacter="f"
<p dir="auto">Next sample:
<pre><code>. LiveWeather.sh
<p dir="auto">App will be displaying weather for <strong>default location, with Celsius degrees and dynamic updates disabled.
<p dir="auto">I hope now you undertand what options consist of.
<h4>Back to code
<p dir="auto">Go back to Notepad++ and write down this code at the end:
<pre><code>while [ $# -gt 0 ]
do
option="$1"
case $option
in
-l) defaultLocation="$2"
shift
shift ;;
-d) dynamicUpdates=1
shift ;;
-f) degreeCharacter="f"
shift ;;
esac
done
<p dir="auto">We have some new interesting points here. At the beginning of this part of code is "<strong>while ... do". While ... do ... done is a loop which executes some code as long as condition between brackets is true.<br />
<strong>$#- at the runtime it contains information about number of given <strong>options.<br />
<strong>-gt- means "greather than"<br />
To sum up, <strong>while [ $# -gt 0 ] means: as long as number of given options is greather than 0 do commands between do ... done.<br />
As you can see each time block inside do ... done is processed <strong>option variable is getting new value <strong>$1. It is <strong>first given option.<br />
Then there is <strong>case ... in ... esac statements. It is great for managing options. When options were provided to app, there must be a way to process it if we want to use them. Every option will be used in other way so thanks to case ... esac statement we can do it very easy: after <strong>option gets new value, check it and do something if equal to: <strong>-l, <strong>-d or <strong>-f.<br />
<strong>$2- <strong>second given option. We use it only if current option is -l (location), because there is argument of option after it.<br />
<strong>shift- removes first option from <strong>options list ($#).<br />
<img src="https://images.hive.blog/768x0/https://steemitimages.com/DQmNVK54e7n17LDSjHRyjfe29kkHbd4LE7LgD56WMng3HT1/exam.png" alt="exam.png" srcset="https://images.hive.blog/768x0/https://steemitimages.com/DQmNVK54e7n17LDSjHRyjfe29kkHbd4LE7LgD56WMng3HT1/exam.png 1x, https://images.hive.blog/1536x0/https://steemitimages.com/DQmNVK54e7n17LDSjHRyjfe29kkHbd4LE7LgD56WMng3HT1/exam.png 2x" />
<h4>Downloading and storing data
<p dir="auto">Next step we have to focus on is data processing, in other worlds how to download and store it. Look at this code and insert it at the end of project:
<pre><code>dataPath="/tmp/wth-$defaultLocation.json"
if [ ! -e $dataPath ];
then
touch $dataPath
data=$(curl "http://api.openweathermap.org/data/2.5/weather?q=$defaultLocation&units=metric&appid=$apiKey")
echo $data > $dataPath
else
data=$(cat $dataPath)
fi
lastUpdateTime=$(($(date +%s) -600))
while true
do
lastfileupdate=$(date -r $dataPath +%s)
if [ $(($(date +%s)-$lastfileupdate)) -ge 600 ];
then
data=$(curl -s "http://api.openweathermap.org/data/2.5/weather?q=$defaultLocation&units=metric&appid=$apiKey")
echo $data > $dataPath
fi
if [ $(($(date +%s)-$lastUpdateTime)) -ge 600 ];
then
lastUpdateTime=$(date +%s)
clear
<p dir="auto"><strong>dataPath- string variable with path where we will keep our data, different for each location, contains location name. Path is: tmp (directory) -> wth-Location.json (data file). For example path for Warsaw is: <strong>/tmp/wth-Warsaw.json.<br />
Line below contains <strong>if then ... else ... fi statement. It check if file with data for chosen location already exists. If created, data from file are being placed into <strong>data variable, otherwise file will be created, then using <strong>curl downloads data and save to created file using redirection (<strong>>).<br />
<strong>lastUpdateTime- keeps as Unix Timestamp (by adding +%s <strong>option) last time when data were received. For example timestamp for 01/04/2018 @ 3:37pm (UTC) is <strong>1515080278. OK, but why is <strong>-600 there? It's neccessary to enforce <strong>first update after execute. 600 seconds = 10 minutes. This way we are cheating and script thinks it's time for update (in thic case displays current weather). If there would not be <strong>-600 we would have to wait 10 mins for first update. Of course there are other ways to solve this issue. :)<br />
Next <strong>while loop is called "infinite loop", because condition <strong>while true is always true. If dynamic updates are disabled, this loop will happen only once, otherwise it will work as long as user wants.<br />
<strong>lastfileupdate- keeps as Unix Timestamp last time when data file were <strong>modified (by adding <strong>-r option and path as argument of option).<br />
Then there are two conditions. First checks if file was updated 10 or more minutes ago (<strong>date +%s returns current time as timestamp). If false downloads data and save it to path. Second condition checks if app was updated 10 or more minutes ago. If false replaces value of <strong>lastUpdateTime with current time.<br />
<strong>clear- clears terminal screen before displaying new data.<br />
<strong>$(something)- returns output of <em>something<br />
<strong>$((something))- returns output of math operations of <em>something
<h4>Reading and displaying data with effects
<p dir="auto">Add the code below:
<pre><code>echo -e '\033[0;30m\033[46m'$(echo $data | jq -r .name)'('$(echo $data | jq -r .coord.lon)','$(echo $data | jq -r .coord.lat)')''\033[40m\033[0m', $(echo $data | jq -r .sys.country)
echo $(echo $data | jq -r .weather[].main)
tempinc=$(echo $data | jq -r .main.temp)
tempcolour=97
if [ $(echo "$tempinc < 0" | bc) -eq 1 ];
then
tempcolour=94
elif [ $(echo "$tempinc >=0 && $tempinc < 10" | bc) -eq 1 ];
then
tempcolour=96
elif [ $(echo "$tempinc >= 10 && $tempinc < 20" | bc) -eq 1 ];
then
tempcolour=92
elif [ $(echo "$tempinc >=20 && $tempinc < 30" | bc) -eq 1 ];
then
tempcolour=93
else
tempcolour=91
fi
temperature=$tempinc
if [ "$degreeCharacter" = "f" ]
then
temperature=$(echo 32+1.8*$tempinc | bc)
fi
echo -e '\033[0;'$tempcolour'm'$(echo $temperature)\\033[0m °${degreeCharacter^^}
wind=$(echo $data | jq .wind.deg)
winddir=$((2193-(${wind%.*}+45)/90))
if [ $winddir -eq 2192 ];
then
winddir=2190
elif [ $winddir -eq 2190 ];
then
winddir=2192
else
:
fi
<p dir="auto"><strong>echo- prints text on the screen. Thanks to <strong>-e option we are able to use visual effects.<br />
<strong>echo $data | jq -r .name- sends <strong>data to <strong>jq (by <strong>|) script to exact <strong>.name value (city name). If you haven't working with API I think it will help you: that's how <strong>data variable looks like (example):
<pre><code>{
"coord": {
"lon": 21.01,
"lat": 52.23
},
"weather": [
{
"id": 803,
"main": "Clouds",
"description": "broken clouds",
"icon": "04n"
}
],
"base": "stations",
"main": {
"temp": 7,
"pressure": 998,
"humidity": 75,
"temp_min": 7,
"temp_max": 7
},
"visibility": 10000,
"wind": {
"speed": 4.6,
"deg": 200
},
"clouds": {
"all": 75
},
"dt": 1515081600,
"sys": {
"type": 1,
"id": 5374,
"message": 0.0032,
"country": "PL",
"sunrise": 1515048267,
"sunset": 1515076689
},
"id": 756135,
"name": "Warsaw",
"cod": 200
}
<p dir="auto"><strong>tempinc- decimal variable that keeps exacted value of current temperature in Celsius degrees.<br />
<strong>\033[0;30m\033[46m and similiar- special codes for colouring text. Complete list of possible foreground and background effects you can find <a href="https://stackoverflow.com/a/28938235/6695735" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">here.<br />
<strong>tempcolour- in default 97 because after when it will be concatenated to <strong>'\033[0;97m' code it will change text colour to white.<br />
Afterwards there are if statements. Depending on current temperature (<strong>tempinc) value of <strong>tempcolour will be replaced with code:
<table><tr><th>Temperature (in Celsius)<th>Code (colour)
<tr><td>Under 0<td>94 (Blue)
<tr><td>Between 0 and 9<td>96 (Cyan)
<tr><td>Between 10 and 19<td>92 (Green)
<tr><td>Between 20 and 29<td>93 (Yellow)
<tr><td>Above 29<td>91 (Red)
<p dir="auto"><strong>temperature- copies current temperature in Celsius from <strong>tempinc variable, in order to calculate Fahrenheit degrees if selected character is equal <strong>f (next <strong>if statement).<br />
<strong>bc- when there is need of using floating-point value it's necessary.<br />
Then thanks to <strong>echo temperature will be displayed.<br />
<strong>${degreeCharacter^^}- changes character to upper case (<strong>f -> <strong>F).<br />
<strong>wind- keeps value of wind direction (in degrees).<br />
<strong>winddir- keeps calculated value of 2193-(<strong>wind value without decimal places+45)/90. 2193 is code of down arrow (↓). This value will be changed to one of arrows, depending on <strong>wind value.<br />
<img src="https://images.hive.blog/768x0/https://steemitimages.com/DQmQLuSjAnvHYaHzhAqjcTGagUUTKF3wC35zdmW39YRcRxe/winddir.png" alt="winddir.png" srcset="https://images.hive.blog/768x0/https://steemitimages.com/DQmQLuSjAnvHYaHzhAqjcTGagUUTKF3wC35zdmW39YRcRxe/winddir.png 1x, https://images.hive.blog/1536x0/https://steemitimages.com/DQmQLuSjAnvHYaHzhAqjcTGagUUTKF3wC35zdmW39YRcRxe/winddir.png 2x" /><br />
<strong>:- "do nothing"<br />
It's time for wind speed and colouring:
<pre><code>windkph=$(echo $data | jq .wind.speed)
windcolour=97
if [ $(echo "$windkph >= 0 && $windkph < 10" | bc) -eq 1 ];
then
windcolour=92
elif [ $(echo "$windkph >= 10 && $windkph < 20" | bc) -eq 1 ];
then
windcolour=93
elif [ $(echo "$windkph >= 20 && $windkph <= 30" | bc) -eq 1 ];
then
windcolour=32
else
windcolour=91
fi
<p dir="auto"><strong>windkph- wind speed in km/h.<br />
<strong>windcolour- similarly as <strong>tempcolour, will ebe used to change colour of wind speed on display thanks to <strong>ifs.
<table><tr><th>Wind speed (in km/h)<th>Code (colour)
<tr><td>Between 0 and 9<td>92 (Green)
<tr><td>Between 10 and 19<td>93 (Yellow)
<tr><td>Between 20 and 30<td>32 (Dark green)
<tr><td>Above 30<td>91 (Red)
<p dir="auto">And the last part of code:
<pre><code>echo -e \\u$winddir '\033[0;'$windcolour'm'$windkph\\033[0m km/h
echo $(echo $data | jq .main.pressure) hPa
echo Humidity: $(echo $data | jq .main.humidity)%
echo Cloud coverage: $(echo $data | jq .clouds.all)%
fi
if [ $dynamicUpdates -eq 0 ];
then
break
fi
done
<p dir="auto">A few <strong>echos in order to display wind speed, pressure, humidity and cloud coverage.<br />
<strong>break- ends loop<br />
In <strong>if statement above if dynamic updates are not enabled the loop will be ended immediately.
<p dir="auto">At now you can save the project as <strong>LiveWeather.sh.
<p dir="auto">There is complete code:
<pre><code>#!/bin/bash
apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
defaultLocation="Warsaw"
dynamicUpdates=0
degreeCharacter="c"
data=0
lastUpdateTime=0
while [ $# -gt 0 ]
do
option="$1"
case $option
in
-l) defaultLocation="$2"
shift
shift ;;
-d) dynamicUpdates=1
shift ;;
-f) degreeCharacter="f"
shift ;;
esac
done
dataPath="/tmp/wth-$defaultLocation.json"
if [ ! -e $dataPath ];
then
touch $dataPath
data=$(curl "http://api.openweathermap.org/data/2.5/weather?q=$defaultLocation&units=metric&appid=$apiKey")
echo $data > $dataPath
else
data=$(cat $dataPath)
fi
lastUpdateTime=$(($(date +%s) -600))
while true
do
lastfileupdate=$(date -r $dataPath +%s)
if [ $(($(date +%s)-$lastfileupdate)) -ge 600 ];
then
data=$(curl -s "http://api.openweathermap.org/data/2.5/weather?q=$defaultLocation&units=metric&appid=$apiKey")
echo $data > $dataPath
fi
if [ $(($(date +%s)-$lastUpdateTime)) -ge 600 ];
then
lastUpdateTime=$(date +%s)
clear
echo -e '\033[0;30m\033[46m'$(echo $data | jq -r .name)'('$(echo $data | jq -r .coord.lon)','$(echo $data | jq -r .coord.lat)')''\033[40m\033[0m', $(echo $data | jq -r .sys.country)
echo $(echo $data | jq -r .weather[].main)
tempinc=$(echo $data | jq -r .main.temp)
tempcolour=97
if [ $(echo "$tempinc < 0" | bc) -eq 1 ];
then
tempcolour=94
elif [ $(echo "$tempinc >=0 && $tempinc < 10" | bc) -eq 1 ];
then
tempcolour=96
elif [ $(echo "$tempinc >= 10 && $tempinc < 20" | bc) -eq 1 ];
then
tempcolour=92
elif [ $(echo "$tempinc >=20 && $tempinc < 30" | bc) -eq 1 ];
then
tempcolour=93
else
tempcolour=91
fi
temperature=$tempinc
if [ "$degreeCharacter" = "f" ]
then
temperature=$(echo 32+1.8*$tempinc | bc)
fi
echo -e '\033[0;'$tempcolour'm'$(echo $temperature)\\033[0m °${degreeCharacter^^}
wind=$(echo $data | jq .wind.deg)
winddir=$((2193-(${wind%.*}+45)/90))
if [ $winddir -eq 2192 ];
then
winddir=2190
elif [ $winddir -eq 2190 ];
then
winddir=2192
else
:
fi
windkph=$(echo $data | jq .wind.speed)
windcolour=97
if [ $(echo "$windkph >= 0 && $windkph < 10" | bc) -eq 1 ];
then
windcolour=92
elif [ $(echo "$windkph >= 10 && $windkph < 20" | bc) -eq 1 ];
then
windcolour=93
elif [ $(echo "$windkph >= 20 && $windkph <= 30" | bc) -eq 1 ];
then
windcolour=32
else
windcolour=91
fi
echo -e \\u$winddir '\033[0;'$windcolour'm'$windkph\\033[0m km/h
echo $(echo $data | jq .main.pressure) hPa
echo Humidity: $(echo $data | jq .main.humidity)%
echo Cloud coverage: $(echo $data | jq .clouds.all)%
fi
if [ $dynamicUpdates -eq 0 ];
then
break
fi
done
<h4>Executing script
<p dir="auto">Open your Linux bash shell and use sample command:
<pre><code>. LiveWeather.sh -l Berlin -d -f
<p dir="auto">Now you can be proud of yourself. :)
<h1>Conclusion
<p dir="auto">I have learnt a lot during creating this project and preparing such big mega-tutorial. I hope it will interesting post for you and it has improve yours knowledge. Let me know if you are interested in articles like this, so <strong>I will be posting more tutorials.<br />
I will be very thankful for upvotes, comments and resteems. Thanks!
Dziękuję panu, właśnie zabieram się do nauki. :)
Its time to make some code. ;_;
Nie panu, jestem tylko studentem.
Bardzo się cieszę, że Cię zmotywowałem. :)
Awesome! :)
Thanks!
$archerbest = "really helpful tutorial please upload more like that";
echo $archerbest;
?>
Thank you. :)
Stay tuned.
welcome following you
Thanks
click here.Congratulations @archerbest, this post is the most rewarded post (based on pending payouts) in the last 12 hours written by a Dust account holder (accounts that hold between 0 and 0.01 Mega Vests). The total number of posts by Dust account holders during this period was 9581 and the total pending payments to posts in this category was $4237.20. To see the full list of highest paid posts across all accounts categories,
If you do not wish to receive these messages in future, please reply stop to this comment.
i will try an make a personal weather app thanks for this