diff --git a/all.sas b/all.sas
index c8dcc6b..3465b9c 100644
--- a/all.sas
+++ b/all.sas
@@ -5092,6 +5092,59 @@ create table &outds (rename=(
run;
%mend mp_getmaxvarlengths;/**
+ @file
+ @brief Performs a text substitution on a file
+ @details Makes use of the GSUB function in LUA to perform a text substitution
+ in a file - either in-place, or writing to a new location. The benefit of
+ using LUA is that the entire file can be loaded into a single variable,
+ thereby side stepping the 32767 character limit in a data step.
+
+ Usage:
+
+ %let file=%sysfunc(pathname(work))/file.txt;
+ %let str=replace/me;
+ %let rep=with/this;
+ data _null_;
+ file "&file";
+ put "&str";
+ run;
+ %mp_gsubfile(file=&file, pattern=str, replacement=rep)
+ data _null_;
+ infile "&file";
+ input;
+ list;
+ run;
+
+ @param file= (0) The file to perform the substitution on
+ @param patternvar= A macro variable containing the Lua
+ [pattern](https://www.lua.org/pil/20.2.html) to search for. Due to the use
+ of special (magic) characters in Lua patterns, it is safer to pass the NAME
+ of the macro variable containing the string, rather than the value itself.
+ @param replacevar= The name of the macro variable containing the replacement
+ _string_.
+ @param outfile= (0) The file to write the output to. If zero, then the file
+ is overwritten in-place.
+
+
SAS Macros
+ @li ml_gsubfile.sas
+
+ Related Macros
+ @li mp_gsubfile.test.sas
+
+ @version 9.4
+ @author Allan Bowe
+**/
+
+%macro mp_gsubfile(file=0,
+ patternvar=,
+ replacevar=,
+ outfile=0
+)/*/STORE SOURCE*/;
+
+ %ml_gsubfile()
+
+%mend mp_gsubfile;
+/**
@file mp_guesspk.sas
@brief Guess the primary key of a table
@details Tries to guess the primary key of a table based on the following logic:
@@ -7571,6 +7624,7 @@ alter table &libds modify &var char(&len);
SAS Macros
@li mf_getplatform.sas
+ @li mf_getuniquefileref.sas
**/
@@ -18749,6 +18803,50 @@ filename &fref1 clear;
%end;
%mend mv_webout;
+/**
+ @file ml_gsubfile.sas
+ @brief Compiles the gsubfile.lua lua file
+ @details Writes gsubfile.lua to the work directory
+ and then includes it.
+ Usage:
+
+ %ml_gsubfile()
+
+**/
+
+%macro ml_gsubfile();
+data _null_;
+ file "%sysfunc(pathname(work))/ml_gsubfile.lua";
+ put 'local fpath, outpath, file, fcontent ';
+ put ' ';
+ put '-- configure in / out paths ';
+ put 'fpath = sas.symget("file") ';
+ put 'outpath = sas.symget("outfile") ';
+ put 'if ( outpath == 0 ) ';
+ put 'then ';
+ put ' outpath=fpath ';
+ put 'end ';
+ put ' ';
+ put '-- open file and perform the substitution ';
+ put 'file = io.open(fpath,"r") ';
+ put 'fcontent = file:read() ';
+ put 'file:close() ';
+ put 'fcontent = string.gsub( ';
+ put ' fcontent, ';
+ put ' sas.symget(sas.symget("patternvar")), ';
+ put ' sas.symget(sas.symget("replacevar")) ';
+ put ') ';
+ put ' ';
+ put '-- write the file back out ';
+ put 'file = io.open(outpath, "w+") ';
+ put 'io.output(file) ';
+ put 'io.write(fcontent) ';
+ put 'io.close(file) ';
+run;
+
+%inc "%sysfunc(pathname(work))/ml_gsubfile.lua";
+
+%mend ml_gsubfile;
/**
@file ml_json.sas
@brief Compiles the json.lua lua file
diff --git a/base/mp_gsubfile.sas b/base/mp_gsubfile.sas
new file mode 100644
index 0000000..e3a0478
--- /dev/null
+++ b/base/mp_gsubfile.sas
@@ -0,0 +1,53 @@
+/**
+ @file
+ @brief Performs a text substitution on a file
+ @details Makes use of the GSUB function in LUA to perform a text substitution
+ in a file - either in-place, or writing to a new location. The benefit of
+ using LUA is that the entire file can be loaded into a single variable,
+ thereby side stepping the 32767 character limit in a data step.
+
+ Usage:
+
+ %let file=%sysfunc(pathname(work))/file.txt;
+ %let str=replace/me;
+ %let rep=with/this;
+ data _null_;
+ file "&file";
+ put "&str";
+ run;
+ %mp_gsubfile(file=&file, pattern=str, replacement=rep)
+ data _null_;
+ infile "&file";
+ input;
+ list;
+ run;
+
+ @param file= (0) The file to perform the substitution on
+ @param patternvar= A macro variable containing the Lua
+ [pattern](https://www.lua.org/pil/20.2.html) to search for. Due to the use
+ of special (magic) characters in Lua patterns, it is safer to pass the NAME
+ of the macro variable containing the string, rather than the value itself.
+ @param replacevar= The name of the macro variable containing the replacement
+ _string_.
+ @param outfile= (0) The file to write the output to. If zero, then the file
+ is overwritten in-place.
+
+ SAS Macros
+ @li ml_gsubfile.sas
+
+ Related Macros
+ @li mp_gsubfile.test.sas
+
+ @version 9.4
+ @author Allan Bowe
+**/
+
+%macro mp_gsubfile(file=0,
+ patternvar=,
+ replacevar=,
+ outfile=0
+)/*/STORE SOURCE*/;
+
+ %ml_gsubfile()
+
+%mend mp_gsubfile;
diff --git a/lua/gsubfile.lua b/lua/gsubfile.lua
new file mode 100644
index 0000000..004ac70
--- /dev/null
+++ b/lua/gsubfile.lua
@@ -0,0 +1,25 @@
+local fpath, outpath, file, fcontent
+
+-- configure in / out paths
+fpath = sas.symget("file")
+outpath = sas.symget("outfile")
+if ( outpath == 0 )
+then
+ outpath=fpath
+end
+
+-- open file and perform the substitution
+file = io.open(fpath,"r")
+fcontent = file:read()
+file:close()
+fcontent = string.gsub(
+ fcontent,
+ sas.symget(sas.symget("patternvar")),
+ sas.symget(sas.symget("replacevar"))
+)
+
+-- write the file back out
+file = io.open(outpath, "w+")
+io.output(file)
+io.write(fcontent)
+io.close(file)
\ No newline at end of file
diff --git a/lua/ml_gsubfile.sas b/lua/ml_gsubfile.sas
new file mode 100644
index 0000000..a39c122
--- /dev/null
+++ b/lua/ml_gsubfile.sas
@@ -0,0 +1,44 @@
+/**
+ @file ml_gsubfile.sas
+ @brief Compiles the gsubfile.lua lua file
+ @details Writes gsubfile.lua to the work directory
+ and then includes it.
+ Usage:
+
+ %ml_gsubfile()
+
+**/
+
+%macro ml_gsubfile();
+data _null_;
+ file "%sysfunc(pathname(work))/ml_gsubfile.lua";
+ put 'local fpath, outpath, file, fcontent ';
+ put ' ';
+ put '-- configure in / out paths ';
+ put 'fpath = sas.symget("file") ';
+ put 'outpath = sas.symget("outfile") ';
+ put 'if ( outpath == 0 ) ';
+ put 'then ';
+ put ' outpath=fpath ';
+ put 'end ';
+ put ' ';
+ put '-- open file and perform the substitution ';
+ put 'file = io.open(fpath,"r") ';
+ put 'fcontent = file:read() ';
+ put 'file:close() ';
+ put 'fcontent = string.gsub( ';
+ put ' fcontent, ';
+ put ' sas.symget(sas.symget("patternvar")), ';
+ put ' sas.symget(sas.symget("replacevar")) ';
+ put ') ';
+ put ' ';
+ put '-- write the file back out ';
+ put 'file = io.open(outpath, "w+") ';
+ put 'io.output(file) ';
+ put 'io.write(fcontent) ';
+ put 'io.close(file) ';
+run;
+
+%inc "%sysfunc(pathname(work))/ml_gsubfile.lua";
+
+%mend ml_gsubfile;
diff --git a/tests/crossplatform/mp_gsubfile.test.sas b/tests/crossplatform/mp_gsubfile.test.sas
new file mode 100644
index 0000000..34c5155
--- /dev/null
+++ b/tests/crossplatform/mp_gsubfile.test.sas
@@ -0,0 +1,33 @@
+/**
+ @file
+ @brief Testing mp_gsubfile.sas macro
+
+ SAS Macros
+ @li mp_gsubfile.sas
+ @li mp_assert.sas
+
+**/
+
+/**
+ * test 1 - simple replace
+ */
+%global str1;
+%let file=%sysfunc(pathname(work))/file.txt;
+%let pat=replace/me;
+%let str=with/this;
+data _null_;
+ file "&file";
+ put "&pat";
+run;
+%mp_gsubfile(file=&file, patternvar=pat, replacevar=str)
+data _null_;
+ infile "&file";
+ input;
+ call symputx('str1',_infile_);
+run;
+
+%mp_assert(
+ iftrue=("&str1"="&str"),
+ desc=Check that simple replacement was successful,
+ outds=work.test_results
+)
\ No newline at end of file