diff --git a/GNUmakefile b/GNUmakefile
index bf094678f0182c464d8355c7fd9161d95287e228..ceef4726e6597f0786e169033ca2070bbce0169a 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -7,6 +7,11 @@ DBDS    += require.dbd
 SOURCES += runScript.c
 DBDS    += runScript.dbd
 
+SOURCES += dbLoadTemplate.y
+DBDS    += dbLoadTemplate.dbd
+
+dbLoadTemplate.c: dbLoadTemplate_lex.c ../dbLoadTemplate.h
+
 #HEADERS += require.h
 
 SOURCES_T2 += strdup.c
diff --git a/dbLoadTemplate.dbd b/dbLoadTemplate.dbd
new file mode 100644
index 0000000000000000000000000000000000000000..721fa85fac3af44f051cea55c26ceea578340e8f
--- /dev/null
+++ b/dbLoadTemplate.dbd
@@ -0,0 +1 @@
+registrar(dbLoadTemplateRegister)
diff --git a/dbLoadTemplate.h b/dbLoadTemplate.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e96e749eff46db37156d669e01332ec8fbd72b8
--- /dev/null
+++ b/dbLoadTemplate.h
@@ -0,0 +1,18 @@
+/*************************************************************************\
+* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
+*     National Laboratory.
+* Copyright (c) 2002 The Regents of the University of California, as
+*     Operator of Los Alamos National Laboratory.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution. 
+\*************************************************************************/
+/* dbLoadTemplate.h */
+
+#ifndef INCdbLoadTemplateh
+#define INCdbLoadTemplateh
+
+#include "shareLib.h"
+epicsShareFunc int dbLoadTemplate(
+    const char *sub_file, const char *cmd_collect, const char *path);
+
+#endif /*INCdbLoadTemplateh*/
diff --git a/dbLoadTemplate.y b/dbLoadTemplate.y
new file mode 100644
index 0000000000000000000000000000000000000000..728913b26d0622127fb590ed2e9c80f3496282a9
--- /dev/null
+++ b/dbLoadTemplate.y
@@ -0,0 +1,489 @@
+%{
+
+/*************************************************************************\
+* Copyright (c) 2006 UChicago, as Operator of Argonne
+*     National Laboratory.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution. 
+\*************************************************************************/
+
+/* for vasprintf */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+
+#include "osiUnistd.h"
+#include "macLib.h"
+#include "dbmf.h"
+
+#include "dbAccess.h"
+#include "dbLoadTemplate.h"
+#include "osiFileName.h"
+#include "epicsVersion.h"
+
+#if defined(vxWorks) || defined (_WIN32)
+#include "asprintf.h"
+#endif
+
+#ifdef BASE_VERSION
+#define EPICS_3_13
+extern void dbLoadRecords(const char*, const char*);
+#else
+#include "iocsh.h"
+#include "epicsExport.h"
+#endif
+
+#if (EPICS_VERSION*10000+EPICS_REVISION*100+EPICS_MODIFICATION<31412)
+#define dbmfStrdup(s) strcpy(dbmfMalloc(strlen((char*)(s))+1),(char*)(s))
+#endif
+
+static int line_num;
+static int yyerror(char* str);
+
+static char *sub_collect = NULL;
+static char *sub_locals;
+static char **vars = NULL;
+static char *db_file_name = NULL;
+static int var_count, sub_count;
+
+/* We allocate MAX_VAR_FACTOR chars in the sub_collect string for each
+ * "variable=value," segment, and will accept at most dbTemplateMaxVars
+ * template variables.  The user can adjust that variable to increase
+ * the number of variables or the length allocated for the buffer.
+ */
+#define MAX_VAR_FACTOR 50
+
+int dbTemplateMaxVars = 100;
+
+%}
+
+%start substitution_file
+
+%token <Str> WORD QUOTE
+%token DBFILE
+%token PATTERN
+%token GLOBAL
+%token EQUALS COMMA
+%left O_PAREN C_PAREN
+%left O_BRACE C_BRACE
+
+%union
+{
+    int Int;
+    char Char;
+    char *Str;
+    double Real;
+}
+
+%%
+
+substitution_file: global_or_template
+    | substitution_file global_or_template
+    ;
+
+global_or_template: global_definitions
+    | template_substitutions
+    ;
+
+global_definitions: GLOBAL O_BRACE C_BRACE
+    | GLOBAL O_BRACE variable_definitions C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "global_definitions: %s\n", sub_collect+1);
+    #endif
+        sub_locals += strlen(sub_locals);
+    }
+    ;
+
+template_substitutions: template_filename O_BRACE C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "template_substitutions: %s unused\n", db_file_name);
+    #endif
+        dbmfFree(db_file_name);
+        db_file_name = NULL;
+    }
+    | template_filename O_BRACE substitutions C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "template_substitutions: %s finished\n", db_file_name);
+    #endif
+        dbmfFree(db_file_name);
+        db_file_name = NULL;
+    }
+    ;
+
+template_filename: DBFILE WORD
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "template_filename: %s\n", $2);
+    #endif
+        var_count = 0;
+        db_file_name = dbmfMalloc(strlen($2)+1);
+        strcpy(db_file_name, $2);
+        dbmfFree($2);
+    }
+    | DBFILE QUOTE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "template_filename: \"%s\"\n", $2);
+    #endif
+        var_count = 0;
+        db_file_name = dbmfMalloc(strlen($2)+1);
+        strcpy(db_file_name, $2);
+        dbmfFree($2);
+    }
+    ;
+
+substitutions: pattern_substitutions
+    | variable_substitutions
+    ;
+
+pattern_substitutions: PATTERN O_BRACE C_BRACE
+    | PATTERN O_BRACE C_BRACE pattern_definitions
+    | PATTERN O_BRACE pattern_names C_BRACE
+    | PATTERN O_BRACE pattern_names C_BRACE pattern_definitions
+    ;
+
+pattern_names: pattern_name
+    | pattern_names COMMA
+    | pattern_names pattern_name
+    ;
+
+pattern_name: WORD
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_name: [%d] = %s\n", var_count, $1);
+    #endif
+        if (var_count >= dbTemplateMaxVars) {
+            fprintf(stderr,
+                "More than dbTemplateMaxVars = %d macro variables used\n",
+                dbTemplateMaxVars);
+            yyerror(NULL);
+        }
+        else {
+            vars[var_count] = dbmfMalloc(strlen($1)+1);
+            strcpy(vars[var_count], $1);
+            var_count++;
+            dbmfFree($1);
+        }
+    }
+    ;
+
+pattern_definitions: pattern_definition
+    | pattern_definitions pattern_definition
+    ;
+
+pattern_definition: global_definitions
+    | O_BRACE C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_definition: pattern_values empty\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+    }
+    | O_BRACE pattern_values C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_definition:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        *sub_locals = '\0';
+        sub_count = 0;
+    }
+    | WORD O_BRACE pattern_values C_BRACE
+    {   /* DEPRECATED SYNTAX */
+        fprintf(stderr,
+            "dbLoadTemplate: Substitution file uses deprecated syntax.\n"
+            "    the string '%s' on line %d that comes just before the\n"
+            "    '{' character is extraneous and should be removed.\n",
+            $1, line_num);
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_definition:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        dbmfFree($1);
+        *sub_locals = '\0';
+        sub_count = 0;
+    }
+    ;
+
+pattern_values: pattern_value
+    | pattern_values COMMA
+    | pattern_values pattern_value
+    ;
+
+pattern_value: QUOTE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_value: [%d] = \"%s\"\n", sub_count, $1);
+    #endif
+        if (sub_count < var_count) {
+            strcat(sub_locals, ",");
+            strcat(sub_locals, vars[sub_count]);
+            strcat(sub_locals, "=\"");
+            strcat(sub_locals, $1);
+            strcat(sub_locals, "\"");
+            sub_count++;
+        } else {
+            fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n",
+                line_num);
+        }
+        dbmfFree($1);
+    }
+    | WORD
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_value: [%d] = %s\n", sub_count, $1);
+    #endif
+        if (sub_count < var_count) {
+            strcat(sub_locals, ",");
+            strcat(sub_locals, vars[sub_count]);
+            strcat(sub_locals, "=");
+            strcat(sub_locals, $1);
+            sub_count++;
+        } else {
+            fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n",
+                line_num);
+        }
+        dbmfFree($1);
+    }
+    ;
+
+variable_substitutions: variable_substitution
+    | variable_substitutions variable_substitution
+    ;
+
+variable_substitution: global_definitions
+    | O_BRACE C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_substitution: variable_definitions empty\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+    }
+    | O_BRACE variable_definitions C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_substitution:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        *sub_locals = '\0';
+    }
+    | WORD O_BRACE variable_definitions C_BRACE
+    {   /* DEPRECATED SYNTAX */
+        fprintf(stderr,
+            "dbLoadTemplate: Substitution file uses deprecated syntax.\n"
+            "    the string '%s' on line %d that comes just before the\n"
+            "    '{' character is extraneous and should be removed.\n",
+            $1, line_num);
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_substitution:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        dbmfFree($1);
+        *sub_locals = '\0';
+    }
+    ;
+
+variable_definitions: variable_definition
+    | variable_definitions COMMA
+    | variable_definitions variable_definition
+    ;
+
+variable_definition: WORD EQUALS WORD
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_definition: %s = %s\n", $1, $3);
+    #endif
+        strcat(sub_locals, ",");
+        strcat(sub_locals, $1);
+        strcat(sub_locals, "=");
+        strcat(sub_locals, $3);
+        dbmfFree($1); dbmfFree($3);
+    }
+    | WORD EQUALS QUOTE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_definition: %s = \"%s\"\n", $1, $3);
+    #endif
+        strcat(sub_locals, ",");
+        strcat(sub_locals, $1);
+        strcat(sub_locals, "=\"");
+        strcat(sub_locals, $3);
+        strcat(sub_locals, "\"");
+        dbmfFree($1); dbmfFree($3);
+    }
+    | QUOTE EQUALS QUOTE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_definition: \"%s\" = \"%s\"\n", $1, $3);
+    #endif
+        strcat(sub_locals, ",\"");
+        strcat(sub_locals, $1);
+        strcat(sub_locals, "\"=\"");
+        strcat(sub_locals, $3);
+        strcat(sub_locals, "\"");
+        dbmfFree($1); dbmfFree($3);
+    }
+    ;
+
+%%
+ 
+#include "dbLoadTemplate_lex.c"
+ 
+static int yyerror(char* str)
+{
+    if (str)
+        fprintf(stderr, "Substitution file error: %s\n", str);
+    else
+        fprintf(stderr, "Substitution file error.\n");
+    fprintf(stderr, "line %d: '%s'\n", line_num, yytext);
+    return 0;
+}
+
+static int is_not_inited = 1;
+
+int dbLoadTemplate(const char *sub_file, const char *cmd_collect, const char *path)
+{
+    FILE *fp;
+    int i;
+
+    line_num = 1;
+    
+    if (!sub_file || !*sub_file) {
+        fprintf(stderr, "must specify variable substitution file\n");
+        return -1;
+    }
+
+    if (dbTemplateMaxVars < 1) {
+        fprintf(stderr,"Error: dbTemplateMaxVars = %d, must be positive\n",
+                dbTemplateMaxVars);
+        return -1;
+    }
+
+    fp = fopen(sub_file, "r");
+    if (!fp && sub_file[0] != OSI_PATH_SEPARATOR[0]) {
+        const char *dirname, *end;
+        int dirlen;
+        char* filename;
+
+        if (!path || !*path) {
+	    path = getenv("EPICS_DB_INCLUDE_PATH");
+        }
+        for(dirname = path; dirname != NULL; dirname = end) {
+            end = strchr(dirname, OSI_PATH_LIST_SEPARATOR[0]);
+            if (end) dirlen = (int)(end++ - dirname);
+            else dirlen = (int)strlen(dirname);
+            if (dirlen == 0) continue; /* ignore empty path elements */
+            if (dirlen == 1 && dirname[0] == '.') continue; /* we had . already */
+            filename = NULL;
+            if (asprintf(&filename, "%.*s" OSI_PATH_SEPARATOR "%s", dirlen, dirname, sub_file) < 0)
+            {
+                fprintf(stderr,"dbLoadTemplate: out of memory\n");
+                break;
+            }
+            fp = fopen(filename, "r");
+            free(filename);
+            if (fp) break;
+        }
+    }
+    if (!fp) {
+        fprintf(stderr, "dbLoadTemplate: error opening sub file %s: %s\n", sub_file, strerror(errno));
+        return -1;
+    }
+
+    vars = malloc(dbTemplateMaxVars * sizeof(char*));
+    sub_collect = malloc(dbTemplateMaxVars * MAX_VAR_FACTOR);
+    if (!vars || !sub_collect) {
+        free(vars);
+        free(sub_collect);
+        fclose(fp);
+        fprintf(stderr, "dbLoadTemplate: Out of memory!\n");
+        return -1;
+    }
+    strcpy(sub_collect, ",");
+
+    if (cmd_collect && *cmd_collect) {
+        strcat(sub_collect, cmd_collect);
+        sub_locals = sub_collect + strlen(sub_collect);
+    } else {
+        sub_locals = sub_collect;
+        *sub_locals = '\0';
+    }
+    var_count = 0;
+    sub_count = 0;
+
+    if (is_not_inited) {
+        yyin = fp;
+        is_not_inited = 0;
+    } else {
+        yyrestart(fp);
+    }
+
+    yyparse();
+
+    for (i = 0; i < var_count; i++) {
+        dbmfFree(vars[i]);
+    }
+    free(vars);
+    free(sub_collect);
+    vars = NULL;
+    fclose(fp);
+    if (db_file_name) {
+        dbmfFree(db_file_name);
+        db_file_name = NULL;
+    }
+    return 0;
+}
+
+#ifndef EPICS_3_13
+#include "registry.h"
+epicsExportAddress(int, dbTemplateMaxVars);
+
+static const iocshFuncDef dbLoadTemplateDef = {
+    "dbLoadTemplate", 3, (const iocshArg *[]) {
+        &(iocshArg) { "filename", iocshArgString },
+        &(iocshArg) { "\"macro=value,...\"", iocshArgString },
+        &(iocshArg) { "searchpath", iocshArgString },
+}};
+
+#ifdef __GNUC__
+/* Without this I somehow always get the original dbLoadTemplate linked instead of my version */
+int __dbLoadTemplate(const char *sub_file, const char *cmd_collect, const char *path) __attribute__ ((weak, alias ("dbLoadTemplate")));
+#endif
+
+static void dbLoadTemplateFunc(const iocshArgBuf *args)
+{
+    __dbLoadTemplate(args[0].sval, args[1].sval, args[2].sval);
+}
+
+typedef struct iocshCommand {
+    iocshFuncDef const    *pFuncDef;
+    iocshCallFunc          func;
+    struct iocshCommand   *next;
+}iocshCommand;
+
+static void dbLoadTemplateRegister(void)
+{
+    static int firstTime = 1;
+    if (firstTime) {
+        iocshRegister(&dbLoadTemplateDef, dbLoadTemplateFunc);
+        firstTime = 0;
+    }
+}
+
+epicsExportRegistrar(dbLoadTemplateRegister);
+#endif
diff --git a/dbLoadTemplate_lex.l b/dbLoadTemplate_lex.l
new file mode 100644
index 0000000000000000000000000000000000000000..afb72951729c104793d8d930377f5095f8ff6232
--- /dev/null
+++ b/dbLoadTemplate_lex.l
@@ -0,0 +1,57 @@
+/*************************************************************************\
+* Copyright (c) 2006 UChicago, as Operator of Argonne
+*     National Laboratory.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution. 
+\*************************************************************************/
+
+newline     "\n"
+backslash   "\\"
+doublequote "\""
+singlequote "'"
+comment     "#"
+whitespace  [ \t\r]
+escape      {backslash}.
+dstringchar [^"\n\\]
+sstringchar [^'\n\\]
+bareword    [a-zA-Z0-9_\-+:./\\\[\]<>;]
+
+%%
+
+"pattern" { return(PATTERN);    }
+"file"    { return(DBFILE);     }
+"global"  { return(GLOBAL);     }
+
+{doublequote}({dstringchar}|{escape})*{doublequote} |
+{singlequote}({sstringchar}|{escape})*{singlequote} {
+    yylval.Str = dbmfStrdup(yytext+1);
+    yylval.Str[strlen(yylval.Str)-1] = '\0';
+    return(QUOTE);
+}
+
+{bareword}+ {
+    yylval.Str = dbmfStrdup(yytext);
+    return(WORD);
+}
+
+"="     { return(EQUALS);       }
+","     { return(COMMA);        }
+"{"     { return(O_BRACE);      }
+"}"     { return(C_BRACE);      }
+
+{comment}.*     ;
+{whitespace}    ;
+{newline}       { line_num++;   }
+
+. {
+    char message[40];
+
+    sprintf(message, "invalid character '%c'", yytext[0]);
+    yyerror(message);
+
+    /* Suppress compiler warning messages */
+    if (0) yyunput('c',NULL);
+    if (0) yy_switch_to_buffer(NULL);
+}
+
+%%