Shell scripting

From GRASS-Wiki
Jump to navigation Jump to search

What is this?

This page contains information about Shell scripting for GRASS GIS.


The shell for the Unix and Unix-like operating systems, is essentially a command(-line) interpreter or processor, a text-based user interface through which one can instruct the operating system of a computer. One or more commands can be written in a script (usually a simple text file) which, in turn, can be read and executed by the shell. The most widespread shell is probably bash.


Scripting is the act of designing, writing, testing, debugging, and maintaining a set of commands which perform specific, from simple to complex, tasks.


Note, content below transfered here from the page GRASS and Shell.

Scripting for GRASS

It is fairly easy to write a GRASS job as Shell script which launches GRASS, does the operation and cleans up the temporary files.


Often, it is convenient to automate repeated jobs. GRASS can be controlled via user scripts to facilitate daily work. How to start? Using the command line is a kind of writing scripts without saving them - so, you may start to write your first script by saving the executed commands in a text file (use your preferred editor to do so, ideally save the script file in ASCII format).

 Important Note: if you don't know shell programming and consider to learn it, 
 better look at Python (e.g. GRASS and Python) to not waste time...


A first GRASS shell script

Comments should be started with a '#' character. The first line indicates the shell interpreter to be used, here "sh" which is always in the /bin/ directory.

Silly example, run within a GRASS session:

#!/bin/sh
# my first script, 
# copyright, year, Author

# plot current region settings
g.region -p

# leave with exit status 0 which means "ok":
exit 0

Save this in a file "myscript.sh" and run it within GRASS GIS from the command line:

sh myscript.sh

It should print the current region settings, finish, and return to the command line.

For shell debugging, run instead:

sh -x myscript.sh

It will echo every line which helps to identify errors.

Example 1: d.rast.region (simple)

Script to set computational region to a raster map ($1 is the parameter given to the script, here map name):

#!/bin/sh
# Author: me, today; copyright: GPL >= 2
# Purpose: Script to set computational region to a raster map
# Usage: d.rast.region rastermap

g.region rast=$1
d.erase
d.rast $1
exit 0

Using the script in a "North Carolina" location GRASS 6 session:

d.mon x0
sh d.rast.region elev_state_500m
sh d.rast.region lsat7_2002_40

Example 2: d.rast.region (improved)

In this example, we assign the first parameter ($1) given to the script (here map name) to a new variable which is easier to understand in the script. Again, the script is setting the computational region to a raster map, but now it says what happens:

#!/bin/sh
# Author: me, today; copyright: GPL >= 2
# Purpose: Script to set computational region to a raster map
# Usage: d.rast.region rastermap

# be careful to not have white space in the next line:
map=$1
g.message message="Setting computational region to map <$map>"
g.region rast=$map
d.erase
d.rast $map
exit 0

Using the script in a "North Carolina" location GRASS 6 session: see Example 1 above.

Example 3: d.rast.region (improved again)

Here we introduce the variable $0 which contains the program name as well as a test to see if the user specified the map to be shown:

#!/bin/sh
# Author: me, today; copyright: GPL >= 2
# Purpose: Script to set computational region to a raster map
# Usage: d.rast.region rastermap

if [ $# -lt 1 ] ; then
   echo "Parameter not defined. Usage"
   echo "   $0 rastermap"
   exit 1
fi

map=$1
g.message message="Setting computational region to map <$map>"
g.region rast=$map
d.erase
d.rast $map
exit 0

To see how it works, it is interesting to use shell debugging:

d.mon x0
sh -x d.rast.region elev_state_500m

Example 4: Parameter handling

Assume you want to create a shell script for GRASS that has two parameters (x and y coordinates) in order to calculate the watershed related to this outlet point (r.water.outlet). In shell, the variables $1 $2 and so on are the parameters you pass on to the script. So, if you have a script called basin.sh and you type

sh basin.sh -23.3 -47.7

the variable $1 will be -23.3 and $2 will be -47.6. So in your script basin.sh you could use the line

r.water.outlet drainage="your_map" basin="basin_map" easting=$2 northing=$1

Likewise, you could also pass a third parameter for the basin map name, etc.

However, it is highly recommended to use g.parser for the parameter handling. It is much easier and then even the graphical user interface will be autocreated and standard messages appear already translated! You can clone from existing scripts, see here for a series of examples.

Using output from GRASS modules in the script

Sometimes you need to use the output of a module for the next step in a script. Most of the GRASS modules which produce numeric output offer a "-g" flag to facilitate the parsing of the results. Along with the "eval" shell function you can reduce the effort of using printed output in the next step to a minimum. The trick is that the equal sign is considered as variable assignment in shell:

  # North Carolina example
  inmap=elevation
  outmap=some_result

Example for common module output:

  g.region rast=$inmap
  r.info -r $inmap
  min=55.57879
  max=156.3299

Using this in a script:

  eval `r.info -r $inmap`
  r.mapcalc "$outmap = float($inmap) / $max"

Verify:

  r.info -r $outmap
  min=0.355522472489405
  max=0.999999772928615

Best practice shell programming

There are many books on the subject, and the subject will be subjective to personal preference, but here are some tips from fellow wiki users. For patches submitted to GRASS, please follow the code guidelines set out in the SUBMITTING files in the main dir of the source code.

  • try to reach 50% of comments, started with # character (see above). Then you will understand your script even after years
  • add an initial comment about what the script does
  • study existing scripts, see here for a series of scripts


Error management:

A module which terminates with a fatal error will return a non-zero exit status, so you can use the command with if, ||, && etc, or test the value of $?.

Inserting coordinates in a raster module from a vector points map

Some raster modules have an option coordinates=, but don't have the option for selecting them from a vector points map. The following example refers to the module r.viewshed.

First, you must have a vector map with the coordinates in the attribute table; if not, add it with v.db.addcolumn and v.to.db.

Example 1: r.viewshed - selecting a single point from vector map

#!/bin/bash
# Author: Marco Curreli
# Usage: sh myviewshed.sh cat
# "cat" is the category of the point from the vector map

VECT=myvector

ARG=1
E_ERR_ARG=65
if [ $# -ne "$ARG" ]
### Verifies the number of arguments ###
then
  echo "Usage: $0 cat"
  exit $E_ERR_ARG
fi

# point coordinates
COR=$(v.db.select -c map=$VECT columns=x,y separator=, where="cat = $1")

r.viewshed --verbose -c in=dtm out=visib_$1 coordinate=$COR obs_elev=2.75 memory=3000 stream_dir=tmp

Example 2: r.viewshed - batch process by selecting points from vector map

#!/bin/bash
# Author: Marco Curreli
# Usage: sh myviewshed_batch.sh cat1,cat2[,cat3,...] [cat3..catN]
# "cat" is the category of the point from the vector map
# example: to selecting cats 1 3 5 6 7
# sh myviewshed_batch.sh 1,3 5..7

VECT=myvector

CAT1=$(
for index in $(eval echo {$1})
do
echo -n "$index "
done

echo
)

CAT2=$(
for index in $(eval echo {$2})
do
echo -n "$index "
done

echo
)

ARG=1
E_ERR_ARG=65
if [ $# -lt "$ARG" ]
then
  echo "Usage: $0 cat1,cat2[,cat3,...] [cat3..catN]
       you must specify at least one argument"
  exit $E_ERR_ARG
fi

# to working with one or two arguments
if [[ -n $2 ]]
then
CATS=$(echo $CAT1 $CAT2)
else
CATS=$(echo $CAT1)
fi

echo categories: $CATS

# list of categories with their coordinates
for i in $CATS
do
v.db.select -c map=$VECT columns=cat,x,y separator=,  where="cat = $i" >> ${VECT}_coord.csv
done

PTI=$(echo $(cat ${VECT}_coord.csv))


for p in $PTI
do
   set -- $p

#coordinates
CORD=$( echo $p | gawk -F "," '{print $2","$3}')

#category
CAT=$( echo $p | gawk -F "," '{print $1}')

r.viewshed --verbose --overwrite in=dtm out=visib_$CAT coordinate=$CORD obs_elev=2.75 memory=1500 stream_dir=tmp

done

rm ${VECT}_coord.csv