Unexpected parsing of SAS macro function - sas

I want to use catx() in %sysfunc(), test code like this:
%let newtext=%sysfunc(catx(%str(, ),%str(grade>80),test text));
%put &=newtext;
%let newtext=%sysfunc(catx(%str(, ),%str(grade<80),test text));
%put &=newtext;
However, SAS take %str(grade>80) as an expression and just resolve it as 1. The result of above code is
NEWTEXT=1, test text
NEWTEXT=0, test text
I want SAS just return the RAW TEXT, that is:
NEWTEXT=grade>80, test text
NEWTEXT=grade<80, test text
How to avoid this kind of unexpected parsing while using %sysfunc() or %qsysfunc()?

Use literal concatenation in macro
%let newtext=grade>80,test text;
Use catx in DATA Step
newtext = catx(',', 'grade>80', 'test text');

Do not use %SYSFUNC() with data step functions like CATX() that allow arguments to be either numbers or strings. That causes %SYSFUNC() to have to make a guess about what type of value the text you are passing it represents so it can tell the data step function what type of arguments it is receiving.
To concatenate in macro code just expand the values next to each other.
%let newtext=grade>80,test text;
To handle the optional insertion of the delimiter that CATX() does add your own macro logic.
%macro catx(dlm,left,right);
%if %length(&left) and %length(&right) %then &left.&dlm.&right ;
%else &left.&right ;
%mend;

Related

SAS: Delete missing items in a macro variable

I have a macro variable called list1, which gives:
%put &list1.;
A,B,C,D,,,G,,I,,
I need to delete those items which are missing i.e empty between the commas. The final output should be like A,B,C,D,G,I. Is it possible? Trim function does not seem to work here. I'm new to SAS so any help will be appreciated here. Thanks.
%let a= A,B,C,D,,,G,,I,,;
data c;
val = tranwrd(trim(compbl(tranwrd("&a",","," "))), " ",",");
call symput('b',val);
run;
%put &b;
This result of A,B,C,D,G,I
Just for fun - pure macro regex version:
%let a= A,B,C,D,,,G,,I,,;
%let regex1 = %sysfunc(prxparse(%str(s/,+/,/)));
%let b=%sysfunc(prxchange(&regex1,-1,%quote(&a)));
%let regex2 = %sysfunc(prxparse(%str(s/,$//)));
%let b=%sysfunc(prxchange(&regex2,-1,%quote(&b)));
%put b= &b;
You can skip the second regex if you never have to deal with trailing commas at the end of your input.
You could convert the commas to spaces and use COMPBL() to collapse the multiple spaces. Then convert the spaces back into commas. In case you have any spaces just swap the comma and the space characters.
%let temp=%sysfunc(translate(%superq(list1),%str( ,),%str(, )));
%let temp=%sysfunc(compbl(%superq(temp)));
%let list2=%sysfunc(translate(%superq(temp),%str( ,),%str(, )));
PS You don't normally want to use comma as the delimiter in macro variables. It makes it difficult to pass the value to other macros or functions since comma is used to separate argument values. Hence all of the macro quoting functions in this code. For variable lists you would normally want a space delimited list where multiple adjacent spaces wouldn't matter. For other types of lists you would normally want another delimiter like | or ^.

Tranwrd just one letter in SAS

How can i quote just one letter in sas?
%sysfunc(tranwrd(%quote(&string),%quote(T),%quote('Test')));
The Problem is, when the string has a 'T' and 'TR' that both get tranwrd to 'Test'
SAS Macro variables are always character. The arguments to macro functions are always character and generally won't require an extra layer of macro macro quoting, and definitely won't if the arguments are to be as literals.
Did you try this first ?
%let string = STACKOVERFLOW;
%let string_tweaked = %sysfunc(tranwrd(&string),T,Test);
%put NOTE: string_tweaked = &string_tweaked;
Do the macro values contain embedded single quotes ?
%let string = %str(S%'T%'ACKOVERFLOW);
%let string_tweaked = %qsysfunc(tranwrd(&string,'T','Test'));
%put NOTE: string_tweaked = &string_tweaked;
The second code sample is analogous to the following DATA step code (whose scope is different than that of the MACRO environment). DATA step string values are explicitly quoted, with either double quote (") or single quote (')
data _null_;
string = "S'T'ACKOVERFLOW";
string_tweaked = tranwrd(string,"'T'","'Test'");
put "NOTE: " string_tweaked=;
run;

transform guid to string for sas put statement

The following code should print a guid to the log:
%let test = %sysfunc(uuidgen());
data _null_;
put &test.;
run;
For some reasons the put line fails. Any ideas? Thanks!
You forgot the %:
%put &test.;
The put statement in the datastep prints either variables or a string literals.
In your case you have a value within a macro variable that you would like the put statement to interpret as a string literal. You can do this by wrapping it in quotes:
data _null_;
put "&test";
run;
If you want to use the macro %put statement. You can just replace the whole datastep with:
%put &test;

SAS: Break up long string in code

I find it good practice to restrict my code to within 80 characters per line. Since SAS ignores white space, this usually isn't a problem. However, I occasionally need to refer to some string which is excessively long.
For example,
filename infile "B:\This\file\path\is\really\long\but\there\is\nothing\I\can\do\about\it\because\it\is\on\a\shared\network\drive\and\I\am\stuck\with\whatever\organization\or\lack\thereof\exists\for\directory\hierarchies\filename.txt";
I can think of two solutions:
1) Insert a carriage return. This however makes the code look quite messy and may unwittingly introduce invisible characters (i.e \r\n) into the string.
filename infile "B:\This\file\path\is\really\long\but\there\is\nothing\
I\can\do\about\it\because\it\is\on\a\shared\network\drive\and\I\am\stuck\
with\whatever\organization\or\lack\thereof\exists\for\directory\hierarchies\
filename.txt";
2) Use macro variables to break the string into several parts.
%let part1 = B:\This\file\path\is\really\long\but\there\is\nothing\;
%let part2 = I\can\do\about\it\because\it\is\on\a\shared\network\drive\and\I\am\stuck\;
%let part3 = with\whatever\organization\or\lack\thereof\exists\for\directory\hierarchies\;
%let part4 = filename.txt;
filename infile "&part1.&part2.&part3.&part4.";
%let path = %sysfunc(pathname(infile));
%put &path;
Ideally, I would like something which allows me to follow the indentation scheme of the rest of the code.
filename infile "B:\This\file\path\is\really\long\but\there\is\nothing\
I\can\do\about\it\because\it\is\on\a\shared\network\drive\and\I\am\stuck\
with\whatever\organization\or\lack\thereof\exists\for\directory\hierarchies\
filename.txt";
A possible solution, at least within the context of this example, would be to bypass a declaration altogether and prompt the use for the input file. This does not appear easy to implement, however.
For this type of situation where the string needs to be used as one token then splitting it into separate macro variables is the best approach.
%let basedir=b:\Main Folder;
%let project=This project\has\many\parts;
%let fname=filename.txt ;
...
infile "&basedir/&project/&fname" ;
Note that SAS is happy to convert your directory delimiters between Unix (/) and Windows (\) style automatically for you.
You could also take advantage of using a fileref to point to a starting point in your directory tree.
filename basedir "&basedir";
...
infile basedir("&project/&fname");
You could also store the path in a text file or dataset and use that to generate the path into a macro variable.
data _null_;
infile 'parameter_file.txt' ;
input filename :$256. ;
call symputx('filename',filename);
run;
...
infile "&filename" ;
Another variation on using macro variable is to use multiple %LET statements to initialize a single macro variable. That way you can break the long string into multiple tokens.
%let fname=B:\This\file\path\is\really\long\but\there\is\nothing;
%let fname=&fname\I\can\do\about\it\because\it\is\on\a\shared\network\drive\and\I\am\stuck;
%let fname=&fname\with\whatever\organization\or\lack\thereof\exists\for\directory\hierarchies;
%let fname=&fname\filename.txt;
Or you could use a DATA step to set your macro variable instead.
data _null_;
call symputx('fname',catx('\'
,'B:\This\file\path\is\really\long\but\there\is\nothing\I\can'
,'do\about\it\because\it\is\on\a\shared\network\drive\and\I\am\stuck'
,'with\whatever\organization\or\lack\thereof\exists\for\directory'
,'hierarchies\filename.txt'
));
run;
For a situation where you need to put a long string in code such as a dataset label or some type of description consider using %cmpres. The function has limits but is useful to keep one inside 80 columns if they can use it. Here, my CR and other adjacent white spaces are being "compressed" in to a single space character.
%macro get_filename(FILEPATH_FILE, FILE)
/DES=%cmpres("returns a file's name, placed into var FILE, removing the
file path from FILEPATH_FILE.");
If you do this a lot, use %SYSFUNC() and COMPRESS() to make a user-defined macro like this:
%macro c(text);
%sysfunc(compress(&text, ,s))
%mend;
filename infile %c("B:\This\file\path\is\really\long\but\there\is\nothing\I\
can\do\about\it\because\it\is\on\a\shared\network\drive\
and\I\am\stuck\with\whatever\organization\or\lack\thereof\
exists\for\directory\hierarchies\and\he\uses\B\as\a\drive\
OMG\who\does\that\filename.txt");
%put %c("B:\This\file\path\is\really\long\but\there\is\nothing\I\
can\do\about\it\because\it\is\on\a\shared\network\drive\
and\I\am\stuck\with\whatever\organization\or\lack\thereof\
exists\for\directory\hierarchies\and\he\uses\B\as\a\drive\
OMG\who\does\that\filename.txt");
Option "s" in the COMPRESS() function removes all whitespace characters.
SAS posts notes on the log, you can ignore them:
NOTE: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation marks.

Multiple search and replace within a string through regular expression in SAS

I have following macro variable:
%let text = abc\pqr{work};
I need to replace \ with \\, { with \{ and } with \} by regular expression.
The code I tried gives error.
%let text = abc\pqr{work};
data _null_;
var=prxchange('s/\\/\\\\/|s/\{/ \\\{/|s/\}/ \\\}/',-1,"&text");
put var;
run;
Also, if the text is "BOLD\ITALIC\ITALICBOLD\BOLDITALIC\B\I\IB\BI", I need to convert it to "\b\i\ib\bi\b\i\ib\bi".
For your first problem, you should be separating each pattern into a separate PRXCHANGE. I don't think PRXCHANGE allows OR operand in it. If run this code, it will work for you:
%let text = abc\pqr{work};
data _null_;
var=prxchange("s/\\/\\\\/",-1,"&text");
var=prxchange("s/\{/\\\{/",-1,var);
var=prxchange("s/\}/\\\}/",-1,var);
put var;
run;
The same principle applies to your second query:
%let text = BOLD\ITALIC\ITALICBOLD\BOLDITALIC\B\I\IB\BI;
data _null_;
var=prxchange("s/BOLD/b/",-1,"&text");
var=prxchange("s/ITALIC/i/",-1,var);
var=lowcase(var);
put var;
run;
Regads,
Vasilij