I’ll let the script speak for itself, there is also a copy on github.

To install the packages needed do:

sudo apt install gmt gmt-gshhg-full cdo wget

Here are some examples of the possible outputs:

cloud_map A gfs synoptic map.

atmp_map A gfs synoptic map with temperature forecast.

#!/bin/bash

             ###################################
             #    Written By Henno Havenga     #
             #           GMT 5.4.3             #
             #          wget 1.19.2            #
             #           CDO 1.9.2             #
             # NCEP/NOAA https://goo.gl/xP3X4L #
             #   SA Shapefile: Natural Earth   #
             #          GPL Licence            #
             ###################################

########################################################################
# DISCLAIMER:                                                          #
# This a simple program to make your own weather forecast using        #
# publicly available Global Forecasting (GFS) data. The main programs  # 
# used include WGET to grab gfs data from the NCEP/NOAA server,        #
# Climate Data Operators (CDO) to convert and modify the data where    #
# needed and the main program used here is Generic Mapping Tools (GMT).#
# GMT is used to perform mathematical functions on the datasets and to # 
# draw and plot the data over the desired area. All data and code is   #
# free to use and share, however, absolutely no part of this script    #
# may be commercialized. If you ask someone money for this you are     #
# breaking my terms for reusing this script.                           #
#                                                                      #
# Please be aware that this is NOT an official weather forecast.       #
# The products created here should not be used in anyway or            #
# form to issue any kind of weather alerts as the South-African Weather#
# Service (SAWS) is the only recognized authority in South-Africa      #
# allowed to do so. It is not the authors job to argue the law. Instead#
# users are encouraged to use the products for education purposes.     #
########################################################################

# The script downloads the data for the current day
# Specify the forecast intervals in the forecast variable
# You can pull a forecast 6 days in advance, that would be 384 hours
today=$(date +"%Y%m%d00")
gfs=http://www.ftp.ncep.noaa.gov/data/nccf/com/gfs/prod/gfs
forecast=(06 12 18 24 30 36 42 48)

# Loop through the specified forecast times wanted and grab GFS data
for i in "${forecast[@]}" 
do 
    wget $gfs.$today/gfs.t00z.pgrb2.0p25.f0$i 
done

# For archive purposes we clip the data to South-Africa with cdo
# and convert GFS files to workable netcdf files
for i in "${forecast[@]}"  
do 
    cdo -sellonlatbox,-35,85,-85,0 gfs.t00z.pgrb2.0p25.f0$i gfs.$today.$i 
    cdo -f nc copy gfs.$today.$i ncfile$i.nc 
done

# Move clipped GFS files to an archive directory 
rm gfs.t00z.pgrb2.0p25.f*
mkdir -p "gfs_archive/gfs_$today"
mv gfs.* gfs_archive/gfs_$today

# Extract variables from the NC files for easy use in GMT
for i in "${forecast[@]}" 
do
    cdo selname,gh ncfile$i.nc geopotential$i.nc 
    cdo splitlevel geopotential$i.nc geopotential$i- 
    mv geopotential$i-050000* zg500$i.nc 
    rm geopotential$i*
done

# To plot the files in GMT we need to get the variables from the newly 
# created netcdf files by extracting only the variable we want.
for i in "${forecast[@]}" 
do
    gmt grdconvert ncfile$i.nc\?prmsl -Gmslp_nrm$i.nc
    gmt grdconvert zg500$i.nc\?gh     -Gzg500_nrm$i.nc
    gmt grdconvert ncfile$i.nc\?tcc   -Gcloudcover$i.nc
    gmt grdconvert ncfile$i.nc\?cape  -Gcape$i.nc
    gmt grdconvert ncfile$i.nc\?aptmp -Gaptmp$i.nc
done

# Do some math
# We standardize the pressure and convert Kelvin to Celsius
for i in "${forecast[@]}" 
do
    gmt grdmath mslp_nrm$i.nc 100 DIV = mslp_nrm$i.nc    
    gmt grdmath zg500_nrm$i.nc 10 DIV = zg500_nrm$i.nc    
    gmt grdmath cloudcover$i.nc 0 NAN = cloudcover$i.nc
    gmt grdmath aptmp$i.nc 273.15 SUB = aptmp$i.nc
done

# Make CAPE values below 250 NAN
for i in "${forecast[@]}"
do
    for number in {0..200}
    do
        gmt grdmath cape$i.nc $number NAN = cape$i.nc
    done
done

            ######################################
            #               Plot                 #
            ######################################

# Coordinates (actually lower left and upper right not E S W N) 
# Projection (Lambert Azimuthal Equal-Area)
coord=(-20/-46/60/-12r)
width=25.5

# First a map with Cloudcover, zg500 and mslp
for i in "${forecast[@]}" 
do
    gmt psbasemap  -R$coord -JA25/-30/$width -Xc -Yc -Ba0 -B+t"Cloud Cover Forecast $(date +"%Y-%m-%d-00:00") + $i Hours" -K > cloud_map$i.ps
    gmt makecpt    -Cgebco -T1/100/5 -Z -I > cloud.cpt
    gmt pscoast    -R -JA -Df -N1 -G200 -W -O -K >> cloud_map$i.ps
    gmt grdimage   -R cloudcover$i.nc -Ccloud.cpt -J -O -Q -K >> cloud_map$i.ps  
    gmt grdcontour -R -JA -Wthinnest,-- mslp_nrm$i.nc -C2 -A2+f10 -T+lLH -O -K >> cloud_map$i.ps
    gmt grdcontour -R -JA -Wthick,black zg500_nrm$i.nc -S150 -C10 -A20+f14 -Gn1 -T+lLH -O -K >> cloud_map$i.ps
    gmt psxy        ZAF_adm1.gmt -R -JA -O -Wfaint -V -K >> cloud_map$i.ps
    gmt psscale    -Ccloud.cpt -R -J -Dx12.25c/-1c+w12c/0.5c+jTC+h -Bx10+l"Cloud Cover" -By+l% -O -K >> cloud_map$i.ps
done

# Make a animation
convert -bordercolor white -border 0 -layers OptimizePlus -density 300 -delay 2x1 cloud*.ps -loop 0 cloud_$(date "+%Y%m%d").gif
convert cloud_$(date "+%Y%m%d").gif -rotate 90  cloud.gif

# Second a map with CAPE, zg500 and mslp
for i in "${forecast[@]}" 
do
    gmt psbasemap  -R$coord -JA25/-30/$width -Xc -Yc -Ba0 -B+t"CAPE Forecast $(date +"%Y-%m-%d-00:00") + $i Hours" -K > cape_map$i.ps
    gmt makecpt    -Chot -T250/2000/50 -Z -I > cape.cpt
    gmt pscoast    -R -JA -Df -N1 -G200 -W -O -K >> cape_map$i.ps
    gmt grdimage   -R cape$i.nc -Ccape.cpt -J -O -Q -K >> cape_map$i.ps  
    gmt grdcontour -R -JA -Wthinnest,-- mslp_nrm$i.nc -C2 -A2+f10 -T+lLH -O -K >> cape_map$i.ps
    gmt grdcontour -R -JA -Wthick,black zg500_nrm$i.nc -S150 -C10 -A20+f14 -Gn1 -T+lLH -O -K >> cape_map$i.ps
    gmt psxy        ZAF_adm1.gmt -R -JA -O -Wfaint -V -K >> cape_map$i.ps
    gmt psscale    -Ccape.cpt -R -J -Dx12.25c/-1c+w12c/0.5c+jTC+h -Bx150+l"CAPE J/Kg" -By+lJ/kg -O -K >> cape_map$i.ps
done

# Make a animation
convert -bordercolor white -border 0 -layers OptimizePlus -density 300 -delay 2x1 cape*.ps -loop 0 cape_$(date "+%Y%m%d").gif
convert cape_$(date "+%Y%m%d").gif -rotate 90 cape.gif

# Third a map with temperature, zg500 and mslp
for i in "${forecast[@]}" 
do
    gmt psbasemap  -R$coord -JA25/-30/$width -Xc -Yc -Ba0 -B+t"Apparent Temperature $(date +"%Y-%m-%d-00:00") + $i Hours" -K > aptmp_map$i.ps
    gmt makecpt    -Cjet -T-8/43/1 -Z > aptmp.cpt
    gmt grdimage   -R aptmp$i.nc -Captmp.cpt -J -O -Q -K >> aptmp_map$i.ps  
    gmt pscoast    -R -JA -Df -N1 -W -O -K >> aptmp_map$i.ps
    gmt grdcontour -R -JA -Wthinnest,-- mslp_nrm$i.nc -C2 -A2+f10 -T+lLH -O -K >> aptmp_map$i.ps
    gmt grdcontour -R -JA -Wthick,black zg500_nrm$i.nc -S150 -C10 -A20+f14 -Gn1 -T+lLH -O -K >> aptmp_map$i.ps
    gmt psxy        ZAF_adm1.gmt -R -JA -O -Wfaint -V -K >> aptmp_map$i.ps
    gmt psscale    -Captmp.cpt -R -J -Dx12.25c/-1c+w12c/0.5c+jTC+h -Bx5+l"Temperature" -By+lCelcuis -O -K >> aptmp_map$i.ps
done

# Make a animation
convert -bordercolor white -border 0 -layers OptimizePlus -density 300 -delay 2x1 aptmp*.ps -loop 0 aptmp_$(date "+%Y%m%d").gif
convert aptmp_$(date "+%Y%m%d").gif -rotate 90  aptmp.gif

# Last map zg500 and mslp
for i in "${forecast[@]}" 
do
    gmt psbasemap  -R$coord -JA25/-30/$width -Xc -Yc -Ba0 -B+t"Synoptic Weather Map $(date +"%Y-%m-%d-00:00") + $i Hours" -K > synoptic$i.ps
    gmt pscoast    -R -JA -Df -N1 -G200 -W -O -K >> synoptic$i.ps
    gmt grdcontour -R -JA -Wthinnest,-- mslp_nrm$i.nc -C2 -A2+f10 -T+lLH -O -K >> synoptic$i.ps
    gmt grdcontour -R -JA -Wthick,black zg500_nrm$i.nc -S150 -C10 -A20+f14 -Gn1 -T+lLH -O -K >> synoptic$i.ps
    gmt psxy        ZAF_adm1.gmt -R -JA -O -Wfaint -V -K >> synoptic$i.ps
done

# Make a animation
convert -bordercolor white -border 0 -layers OptimizePlus -density 300 -delay 2x1 synoptic*.ps -loop 0 synoptic_$(date "+%Y%m%d").gif
convert synoptic_$(date "+%Y%m%d").gif -rotate 90 synoptic.gif

# Move eps and gif 
mkdir -p "ps/$today"
mkdir -p "gif/$today"
mv *ps ps/$today
rm *$(date "+%Y%m%d").gif
mv *gif gif/$today

# The data is quite big, because the netcdf files are a product of the
# gfs files you can remove all netcdf files and compress the gfs
# file for archive purposes.
rm *.nc

exit 0