I have a bash script, where I need to get the user's geolocation (latitude, longitude and country code). If a GPS device is present, I use gpspipe. But a lot of laptops/computers don't have GPS devices (yet). I need the location as accurate as possible, and getting the geolocation by IP address is (by far) too unreliable.
I ended up with the following bash script with the following dependencies: Python with sys, urllib and json libraries, wget and a working internet connection.
LAT=""
LNG=""
CTRY=""
IFS=$'\n'
printf "Give your location (street name, city, country, etc): "
read LOCATION
while [ -z "$LAT" ]; do
printf "Querying Google Geo API... "
LOCATION=$(echo $LOCATION | python -c "exec(\"import urllib,sys;\\nprint(urllib.quote_plus(sys.stdin.readline()))\")")
URL="https://maps.googleapis.com/maps/api/geocode/json?address=$LOCATION"
GOOGLEAPIS=$(wget -qO- $URL)
STATUS=$(echo $GOOGLEAPIS | python -c "exec(\"import json,sys;\\nobj=json.load(sys.stdin);\\nprint(obj['status'])\")")
case "$STATUS" in
*OK*)
OPTIONS=($(echo $GOOGLEAPIS | python -c "exec(\"import json,sys;\\nobj=json.load(sys.stdin);\\nfor x in obj['results'][::]:print(x['formatted_address'])\")"))
OPT=$OPTIONS
NRFOUND=${#OPTIONS[@]}
IDX="0"
if [[ $NRFOUND -ge 2 ]]; then
printf "More than one option found. Please select your location from the list:\n"
select OPT in "${OPTIONS[@]}";
do
IDX=$(($REPLY - 1))
break
done
fi
LAT=$(echo $GOOGLEAPIS | python -c "exec(\"import json,sys;\\nobj=json.load(sys.stdin);\\nprint(obj['results'][$IDX]['geometry']['location']['lat'])\")")
LNG=$(echo $GOOGLEAPIS | python -c "exec(\"import json,sys;\\nobj=json.load(sys.stdin);\\nprint(obj['results'][$IDX]['geometry']['location']['lng'])\")")
CTRY=$(echo $GOOGLEAPIS | python -c "exec(\"import json,sys;\\nobj=json.load(sys.stdin);\\nfor x in obj['results'][$IDX]['address_components']:\\n\\tif x['types'][0] == 'country':print(x['short_name'])\")")
break
;;
*)
printf "Google could not find your location based on your query. Google says: $STATUS\nPlease enter another location description.\n"
read LOCATION
;;
esac
done
unset $IFS
printf "Selected location: %s\nYour latitude/longitude location set to: (%s, %s).\nYour countycode is set to: %s\n" $OPT $LAT $LNG $CTRY
The code works (as required here on CR) and a sensible geolocation is found for a lot of queries. But, as I'm no seasoned shell writer, I'd like your review on this code. Do you see any pitfalls, corrections, improvements (on the coding), etc?
1 Answer 1
Bugs:
echo "$my_variable" | my_command
can behave unexpectedly if the variable starts with one of theecho
options. You can domy_command <<< "$my_variable"
instead.unset $IFS
does not unsetIFS
. For that you needunset IFS
.
Simplifications:
- You shouldn't need to set
IFS
-read
will save the full input to the variable anyway (seehelp [r]ead
). - A lot of the code is already Python, and Python has much fewer caveats than Bash, so changing to it would very much benefit the maintainability of the code. Strictly speaking it's not a pure Bash script anyway, and you really don't want to be parsing JSON manually in Bash. If you really need a Bash script (which you shouldn't unless it's for a Bash programming exercise, and even then, somebody is wrong on the Internet if they asked you to use pure Bash to parse JSON) you could make it a wrapper script.
- Bash's
read
takes a-p prompt
option, so you can avoid the firstprintf
. - Unchanging variables such as
URL
should be assigned outside of the loop.
Style issues:
- Non-exported variables should be lowercase to distinguish them from exported variables.
- It is customary to
exit
with a non-zero exit code when a command fails.
-
6\$\begingroup\$ All good points. I'd especially emphasize: Just do this in Python since it's already a dependency. \$\endgroup\$Edward– Edward2014年09月25日 12:42:42 +00:00Commented Sep 25, 2014 at 12:42
-
1\$\begingroup\$ Wow! Great comments! I understand and agree with all of them. I think I'll rewrite it in Python indeed. Again: thanks for your excellent review! \$\endgroup\$agtoever– agtoever2014年09月25日 12:55:07 +00:00Commented Sep 25, 2014 at 12:55