I have a lot of 6-digit numbers in a SAS program:
898300 898311 898312 898313 898314 898315 898316 898317 898321 898322 898323 898324 898331 898332 898333 898341 898342 898343
898400 898401 898402 898403 898500 898501 898502 898503 898600 898601 898602 898603 898604 898605 898606 898607 898608 898609
898610 898611 898612 898613 898614 898615 898616 898617 898700 898701 898702 898703 898704 898705 898706 898800 898801 898901
I would like to do a quick find and replace using Ctrl+H such that alle the 6-digit numbers are "quoted":
"898300" "898311" "898312" ...
etc.
I think doing a regular expression search is the way to go, but I am not able to identify the specific syntax. Anyone who knows what to do?
Thanks
Am sure this could be done in notepad (replace all multiple spaces with one, then replace a single space with " ") but seeing as you tagged SAS here is a SAS solution.
first, compile this macro:
/***
Converts a space delimited string into one with custom quotes / delimiters
#usage
%put %get_quoted_str(in_str=blah blah blah
,dlm=%str(,)
,quote=%str(%') );
returns: 'blah','blah','blah'
##
***/
%macro get_quoted_str(IN_STR=,DLM=,QUOTE=);
%local i item buffer;
%let i=1;
%do %while (%qscan(&IN_STR,&i,%str( )) ne %str() ) ;
%let item=%scan(&IN_STR,&i,%str( ));
%if %bquote("E) ne %then %let item="E%trim(&item)"E;
%else %let item=%trim(&item);
%if (&i = 1) %then %let buffer =&item;
%else %let buffer =&buffer&DLM&item;
%let i = %eval(&i+1);
%end;
&buffer
%mend;
then call as follows:
%put %get_quoted_str(IN_STR=898300 898311 898312 898313 898314 898315 898316 898317 898321 898322 898323 898324
898331 898332 898333 898341 898342 898343 898400 898401 898402 898403 898500 898501 898502 898503 898600 898601
898602 898603 898604 898605 898606 898607 898608 898609 898610 898611 898612 898613 898614 898615 898616 898617
898700 898701 898702 898703 898704 898705 898706 898800 898801 898901
,DLM=%str( ),QUOTE=%str(%")
);
which gives:
"898300" "898311" "898312" "898313" "898314" "898315" "898316" "898317" "898321" "898322" "898323" "898324" "898331" "898332"
"898333" "898341" "898342" "898343" "898400" "898401" "898402" "898403" "898500" "898501" "898502" "898503" "898600" "898601"
"898602" "898603" "898604" "898605" "898606" "898607" "898608" "898609" "898610" "898611" "898612" "898613" "898614" "898615"
"898616" "898617" "898700" "898701" "898702" "898703" "898704" "898705" "898706" "898800" "898801" "898901"
The above can then be copy pasted back into the program..
As long as the result is shorter than %SYSFUNC() limits I normally just use TRANWRD() function call for this. Compress multiple blanks to one first using COMPBL().
%let list=A B C D ;
%let qlist="%sysfunc(tranwrd(%sysfunc(compbl(&list)),%str( )," "))" ;
Related
I have a SAS list. This SAS list is stored in a macro variable. Please assume that I have no table to derive this SAS list.
The SAS list contains names separated by commas. An example of the SAS list macro variable:
%LET sas_list = name1,name2,name3;
I want to check whether macro-variable “item” is present in the list.
Something like:
%IF &item. IN &sas_list. %THEN %DO;
Whatever;
%END;
For some reason, I get the error:
“A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: name1 IN name1,name2,name3”
Help is appreciated.
You need to use two options here:
MINOPERATOR, this will allow the use of the IN operator
MINDELIMITER, this will allow you to set the delimiter
%let sas_list = name1,name2,name3;
options minoperator mindelimiter=',';
%Macro want(item);
%if &item. in &sas_list. %then %put i = 1;
%else %put i = 0;
%mend;
%want(name1);
i = 1
Add the minoperator and mindelimiter system options. These stand for Macro IN Operator and Macro IN Delimiter.
options minoperator mindelimiter=',';
%LET sas_list = name1,name2,name3;
%LET item = name1;
%IF &item. IN &sas_list. %THEN %DO;
%put &item is in &sas_list;
%END;
Output:
name1 is in name1,name2,name3
If you want to find values that are not in a list, pass it through %eval.
options minoperator mindelimiter=',';
%LET sas_list = name1 name2 name3;
%LET item = name4;
%IF %eval(&item. IN &sas_list.) = 0 %THEN %DO;
%put &item NOT in &sas_list;
%END;
Output:
name4 is NOT in name1,name2,name3
Note that you can also supply these options directly in a macro if you only want in to work selectively. For example:
%macro foo / minoperator mindelimiter=',';
...
%mend;
You can also use the FINDW funtion to ascertain the presence of an item in a list.
%if %sysfunc(FINDW(%upcase(%superq(saslist)), %upcase(&item), %str(,))) %then %do;
...
%end;
Supporting
%LET sas_list = name1,name2,name3;
you can find if name2 is in the list this way.
data test;
it = findw("&sas_list", 'name2',',');
run;
but I assume you want to find it out without using a data step, so
%let het = %sysfunc(findw(%quote(&sas_list),name2,%quote(,)));
%put NOTE: het is &het;
does the job. Note
you find is not a macro function, so you need %sysfunc() to call it in a macro statement
you have to mask the comma's with %quote(). Otherwise they would be considered argument separators.
I'm unable to remove the parentheses and text within parentheses using %sysfunc(prxchange(...)). See example
%macro test(col=);
%local result;
%let result = %sysfunc(prxchange(s|\([^\)]+\)||i, -1, &col.));
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.))
ERROR: Expected close parenthesis after macro function invocation not found.
The expected output should be try remove (ignoring double spaces)
EDIT - thanks to #user667489, the simplest fix for this is
%macro test(col=);
%local result;
%let result = %sysfunc(compbl(%sysfunc(prxchange(s|%quote(\%([^\%)]+\%)||i), -1, &col.))));
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.));
I found a way of getting this to work more or less as-is:
%macro test(col=);
%local result regex;
%let regex = %sysfunc(prxparse(%str(s/\%([^\%)]+\%)//)));
%let result = %sysfunc(prxchange(®ex, -1, &col.));
%syscall prxfree(regex); /*Prevent memory leak*/
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.));
Masking the brackets within the regex with % symbols to prevent them from being parsed as SAS code and using a separate prxparse seems to do the trick.
Not sure if you can fix this because of the nature of how %sysfunc() has to convert the macro code into values to push in the function you are trying to call.
Why not just leave the PRXCHANGE() function call in actual SAS code instead?
For example you could have your macro generate a DATA step. I would recommend just passing in the NAME of the macro variable that has the value text and the NAME of the macro variable you want to assign the result into.
%macro test(invar,outvar);
%if not %symexist(&outvar) %then %global &outvar;
data _null_;
call symputx("&outvar",prxchange('s|\([^\)]+\)||i', -1,symget("&invar")));
run;
%mend test;
%let string = try (to) remove (this);
%test(invar=string,outvar=result);
%Put &=result;
Also try this:
%macro string(string);
%local new_string;
%let new_string=%sysfunc(prxchange(s/\s?\(\w+\)//,-1,&string));
%put &new_string;
%mend;
%string(%str(try (to) remove (this)));
When calling CATT() function with %sysfunc, is there a way to stop it from evaluating an expression?
For example given the code:
%let date=10-13-2015;
%put %sysfunc(catt(The date Is:,&date));
I would like it to return:
The date Is:10-13-2015
Because 10-13-2015 is just a text string. But instead CATT() sees hyphen as a subtraction sign and evaluates it as a numeric expression, returning:
The date Is:-2018
I have tried macro quoting, but doesn't change anything, I suppose because I need to somehow hide the values from CATT(). Seems if any argument to CATT looks like an expression, it will be treated as such.
Another example:
%let value=2 and 3;
%put %sysfunc(catt(The value Is:,&value));
The value Is:1
Provided you can do so, just remove the comma - there's no need to separate it into an individual parameter (unless you're using catx() rather than catt():
%let date=10-13-2015;
%put %sysfunc(catt(The date Is: &date));
Personally, I think the best way to work is to store the date as a SAS date value and then use the second (optional) parameter of %sysfunc to apply the formatting. This provides better flexibility.
%let date = %sysfunc(mdy(10,13,2015));
%put The date Is: %sysfunc(sum(&date),mmddyyd10.);
If you are insistent on the original approach and are using catx(), then I don't know how to do it exactly. The closest I could get was to insert a piece of text so it couldn't be interpreted as an expression, and then remove that text afterwards using tranwrd. Pretty, ugly, and it leaves a space:
%let date=10-13-2015;
%let tmp=%sysfunc(catx(#, The date Is: , UNIQUE_STRING_TO_REMOVE&date ));
%let want=%sysfunc(tranwrd(&tmp, UNIQUE_STRING_TO_REMOVE, ));
%put &want;
Gives:
The date Is:# 10-13-2015
I also tried every combination of macro quoting, and scanned through the entire SAS function list and couldn't see any other viable options.
I don't see an easy way around this, unfortunately. I do see that you could in theory pass this through an FCMP function, though since FCMP doesn't allow true variable arguments, that isn't ideal either, but...
proc fcmp outlib=work.funcs.funcs;
function catme(delim $, in_string $) $;
length _result $1024;
length _new_delim $1;
_new_delim = scan(in_string,1,delim);
do _i = 1 to countc(in_string,delim);
_result = catx(_new_delim, _result, scan(in_string,_i+1,delim));
end;
return(_result);
endfunc;
quit;
options cmplib=work.funcs;
%let date=10-13-2015;
%put %sysfunc(catme(|,:|The date Is| &date.));
Or add quotes to the argument and then remove them after the CATx.
%sysfunc(dequote(%sysfunc(catt(.... ,"&date."))))
All messy.
The problem with %SYSFUNC() evaluating the arguments is not limited to the CAT() series of functions. Any function that accepts numeric values will result in SAS attempting to evaluate the expression provided.
This can be a useful feature. For example:
%let start_dt=10OCT2012 ;
%put %sysfunc(putn("&start_dt"d +1,date9));
You don't need to use CAT() functions to work with macro variables. Just expand the values next to each other and the are "concatenated".
%let date=10-13-2015;
%put The date Is:&date;
If you want to make a macro that works like the CATX() function then that is also not hard to do.
%macro catx /parmbuff ;
%local dlm return i ;
%if %length(&syspbuff) > 2 %then %do;
%let syspbuff = %qsubstr(&syspbuff,2,%length(&syspbuff)-2);
%let dlm=%qscan(&syspbuff,1,%str(,),q);
%let return=%qscan(&syspbuff,2,%str(,),q);
%do i=3 %to %sysfunc(countw(&syspbuff,%str(,),q));
%let return=&return.&dlm.%qscan(&syspbuff,&i,%str(,),q);
%end;
%end;
&return.
%mend catx;
%put %catx(|,a,b,c);
a|b|c
%put "%catx(",",a,b,c,d)";
"a","b","c","d"
Slightly less insane function-style macro without the dosubl:
%macro catx() /parmbuff;
%local rc dlm i params OUTSTR QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);
%let i = 1;
%let QWORD = %scan(&PARAMS,&i,%str(,));
%let OUTSTR = &QWORD;
%do %while(&QWORD ne);
%let i = %eval(&i + 1);
%let QWORD = %scan(&PARAMS,&i,%str(,));
%if &QWORD ne %then %let OUTSTR = &OUTSTR.&DLM.&QWORD;
%end;
%unquote(&OUTSTR)
%mend catx;
%put %catx(%str( ),abc,10 - 1 + 2,def);
Somewhat more insane but apparently working option - use %sysfunc(dosubl(...)) and lots of macro logic to create a function-style macro that takes input in the same way as %sysfunc(catx(...)), but forces catx to treat all input as text by quoting it and calling it in a data step.
%macro catxt() /parmbuff;
%local rc dlm i params QPARAMS QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);
%let i = 1;
%let QWORD = "%scan(&PARAMS,&i,%str(,))";
%let QPARAMS = &QWORD;
%do %while(&QWORD ne "");
%let i = %eval(&i + 1);
%let QWORD = "%scan(&PARAMS,&i,%str(,))";
%if &QWORD ne "" %then %let QPARAMS = &QPARAMS,&QWORD;
%end;
%let rc = %sysfunc(dosubl(%str(
data _null_;
call symput("OUTSTR",catx("&dlm",%unquote(&QPARAMS)));
run;
)));
&OUTSTR
%mend catxt;
%put %catxt(%str( ),abc,10 - 1 + 2,def);
Although this uses a data step to execute catx, dosubl allows the whole thing to be run in any place where you could normally use %sysfunc(catx(...)).
I want to use a Macro call in a data step. Below is the macro and its invocation in a data step but this isnt working. Can you guys please suggest a way to make it work.
%macro xscan(string, delimiter, word_number);
%let len1=%length(&string); /*Computing the length of the string*/
%let len=%eval(&len1+1);
%let sub=%scan(&string,&word_number,"&delimiter");
%if &word_number ge 0 %then %do;
%let pos=%index(&string,&sub); /* Locate the position while reading left to right*/
%end;
%if &word_number lt 0 %then %do;
data _null_;
pos=find("&string","&sub",-&len);
call symput("pos",pos);
run;
%end;
%let strg=%substr(&string,&pos); /* Extract the substring*/
%put the string is &strg;
%mend;
data work.in_data;
length in_string $50;
in_string = “a bb ccc dddd bb eeeee”;
output;
in_string = “aa b cc aa dee”;
output;
run;
data work.out_data;
set work.in_data;
length sub_str $50;
start_word_num = -(_n_ +1);
sub_str = %xscan(in_string,’ ‘, start_word_num);
run;
proc print; run;
If the macro is to be used inside a datastep, write it more simply using just datastep functions instead of making it complicated with macro functions. There are plenty of SAS string functions which will allow you to accomplish what it appears you want in far less code.
Data steps are very flexible and they allow you to manipulate your data in a very flexible day, I would recommend you trying to refactor the code to use only a datastep, however, if you still want to use it the way you have it right now use call execute .
Here is an example:
data _null_ ;
input name $ value !$ ;
call execute
( ‘%global ‘
llname~l ‘;’ II
‘%let’ Ilnamell =’ [1
value II ‘;’
);
cards ;
abc xyz
;
Refer to these docs for further reading :
http://www2.sas.com/proceedings/sugi30/154-30.pdf and
http://www2.sas.com/proceedings/sugi22/CODERS/PAPER70.PDF
I want use a macro in a %let call, Below is the Macro code and how I want to invoke it. Please help me achieve it.
%macro xscan(string, delimiter, word_number);
%let len1=%length(&string); /*Computing the length of the string*/
%let len=%eval(&len1+1);
%let sub=%scan(&string,&word_number,"&delimiter"); /*Fetch the string specified by word_number*/
%if &word_number ge 0 %then %do;
%let pos=%index(&string,&sub); /* Locate the position while reading left to right*/
%end;
%if &word_number lt 0 %then %do;
data _null_;
pos=find("&string","&sub",-&len); /* Locate the position while reading from right to left*/
call symput("pos",pos);
run;
%end;
%let strg=%substr(&string,&pos); /* Extract the substring*/
%put the string is &strg;
%mend;
%let sub_str = %xscan(a bb ccc dddd bb eeeee, %str( ), -2);
%put The value of sub_str = &sub_str;
Desired implementation:
data work.in_data;
length in_string $50;
in_string = “a bb ccc dddd bb eeeee”;
output;
in_string = “aa b cc aa dee”;
output;
run;
data work.out_data;
set work.in_data;
length sub_str $50;
start_word_num = -(_n_ +1);
sub_str = %xscan(in_string,’ ‘, start_word_num);
run;
proc print; run;
I'm posting a new answer since the other answer answers a slightly different question.
Here, your macro really is intended to perform data step techniques, not macro techniques. You cannot (easily) use a macro to edit variable contents; a macro is intended to write SAS code, not to modify variables. You could use PROC FCMP to solve this problem, and I may well do so if I have more time, but for now here's the proper solution with just data step techniques and a normal (non-functional) macro.
First, write the data step technique to accomplish it. This is a fairly messy but effective solution. It only works for negative start_word_num; if left or right is desired it would need some modification to the loop parameters. I suggest using this as a starting point and improving it for your needs.
data work.out_data;
set work.in_data;
length sub_str $50;
start_word_num = -(_n_ +1);
do _t = countc(trimn(in_string),' ')+1 to countc(trimn(in_string),' ')+start_word_num+2 by -1;
sub_str = catx(' ',scan(in_string,_t,' '),sub_str);
put _t= sub_str=;
end;
put in_string= sub_str=;
run;
Now, move the loop into a macro.
%macro xscan(word_num, initial_string, result);
&result.=' ';
do _t = countc(trimn(&initial_string.),' ')+1 to countc(trimn(&initial_string.),' ')+&word_num.+2 by -1;
&result. = catx(' ',scan(&initial_string.,_t,' '),&result.);
end;
%mend xscan;
data work.out_data;
set work.in_data;
length sub_str $50;
start_word_num = -(_n_ +1);
%xscan(start_word_num,in_string,sub_str);
put in_string= sub_str=;
run;
You have two problems. First off, a function-style macro must not contain any data steps (or procs or anything else). If you do need to execute a data step, you have to use FCMP with run_macro. However, here you can use %SYSFUNC to accomplish what you are doing in the data step.
Second, you need to actually return the value. Ultimately a macro resolves to text, so you need to resolve
%let x = %xscan(...);
to
%let x = bb eeeee;
So you need to simply have bb eeeee as open text in your macro.
This should accomplish both things:
options mprint symbolgen;
%macro xscan(string, delimiter, word_number);
%local len1 len sub pos;
%let len1=%length(&string); /*Computing the length of the string*/
%let len=%eval(&len1+1);
%let sub=%scan(&string,&word_number,"&delimiter"); /*Fetch the string specified by word_number*/
%if &word_number ge 0 %then %do;
%let pos=%index(&string,&sub); /* Locate the position while reading left to right*/
%end;
%else %if &word_number lt 0 %then %do;
%let pos=%sysfunc(find(&string,&sub,-&len)); /* Locate the position while reading from right to left*/
%end;
%substr(&string,&pos) /* Extract the substring*/
%mend;
%let sub_str = %xscan(a bb ccc dddd bb eeeee, %str( ), -2);
%put The value of sub_str = &sub_str;
(Note, I don't necessarily know this does what you really want, but it does what the code appears to be doing.)
Some tips for function-style macros, courtesy of Rob Penridge:
Define all of your macro variables using a %local statement like so: %local len1 len sub pos;. That way you do not overwrite global macro variables.
Use /* THIS STYLE FOR COMMENTING */. Using other comment styles may cause the line to end.
The secret to making the macro work is the line that uses %substr at the end. This resolves to bb eeeeee being left in open code. Since that is all that is left, that is what calling the macro resolves to.
Do not put a semicolon on the line that is actually returned, as it may be undesirable when the function-style macro is used.