SAS通过PRX系列函数(如PRXPARSE、PRXMATCH)支持正则表达式,用于高效匹配、查找、提取和替换文本模式,构成正则表达式语句的主要结构是元素和函数。
常用元素
构成正则表达式的语句有很多元素:
.(点): 匹配除换行符外的任意单个字符。 \d: 匹配任意一个数字 (等同于 [0-9])。\w: 匹配任意一个单词字符 (字母、数字或下划线)。 \s: 匹配任意一个空白字符 (空格、制表符等)。 ^: 匹配字符串的开头。 $: 匹配字符串的结尾。 […]: 匹配方括号中包含的任意一个字符。例如 [abc]匹配 "a", "b", 或 "c"。(…): 将模式分组,作为一个单元使用。 *: 匹配前面的元素零次或多次。 +: 匹配前面的元素一次或多次。 ?: 匹配前面的元素零次或一次。 |: 或逻辑操作符。例如 a|b匹配 "a" 或 "b"。
这些元素可以不用刻意记,在后面的代码中边练边记、边用边记就好。
常用函数
除了上述需要了解的基本元素,我们还需要知道SAS中几个常用的正则表达式相关函数:
PRXPARSE
解析表达式用的,一般认为是执行后续的第一步,程序里常用prxparse(pattern)构建一个语句,再放在其他函数里用。实际上直接写语句不用prxparse也是可以的。
写正则表达式的步骤:
-
先写一个冒号: '' -
在冒号里面加两个斜杠: '//' - 接下来在斜杠里面输入需要匹配的模式即可
-
两个 //是需要匹配的内容,如果涉及符号、数字等就用【\】来进行转译。
比如匹配多个数字就是:'/\d+/'
匹配冒号:'/:/'
示例
/* 再比如匹配+和后面的一堆数字:*/
DATA _NULL_;
text = "Product_ID: 12345";
/* 解析模式 */
pattern_id = PRXPARSE('/\d+/');
/* 检查是否匹配 */
match_pos = PRXMATCH(pattern_id, text);
IF match_pos > 0 THEN PUT "字符串中找到了数字,起始位置在: " match_pos;
ELSE PUT "字符串中没有找到数字。";
RUN;
PRXMATCH
用来检查指定字符串是否在变量中存在,如果存在就返回匹配开始的位置,否则为0。
示例1:找到指定字符的位置
DATA _NULL_;
text = "Product_ID: 12345";
/* 解析模式 */
pattern_id = PRXPARSE('/\d+/');
/* 检查是否匹配 */
match_pos = PRXMATCH(pattern_id, text);
IF match_pos > 0 THEN PUT "字符串中找到了数字,起始位置在: " match_pos;
ELSE PUT "字符串中没有找到数字。";
RUN;
示例2:条件判断
在做SDTM数据集的时候prxmatch有一个很实用的点:if prxmatch('/(y|yes)\s*/i',tuyn_std);
用正则表达式可以将[y|Y|yes|Yes|YES]等等很多种情况的判断到。
PRXSUBSTR
提取匹配的字符串
示例1:提取匹配字符串内容
DATA products;
INPUT description $50.;
CARDS;
Order for P1234 is complete.
Item P5678 is on backorder.
Invalid code PX999.
;
RUN;
DATA extracted_codes;
SET products;
/* 模式: 字母P后跟4个数字 \d{4} */
pattern_id = PRXPARSE('/P\d{4}/');
IF pattern_id > 0 THEN DO;
CALL PRXSUBSTR(pattern_id, description, position, length);
IF position > 0 THEN DO;
product_code = SUBSTR(description, position, length);
END;
END;
KEEP description product_code;
RUN;
PROC PRINT DATA=extracted_codes;
RUN;
PRXCHANGE
搜索与替换
语法:new_string = PRXCHANGE(pattern_id, times, source_string);
示例1:将字符串中的A换成B。
DATA _NULL_;
text = "I like apple, APPLE, and AppLe.";
/* 模式: s/apple/orange/i */
/* s/.../.../ 是替换格式 */
/* i 是一个修饰符,表示不区分大小写 */
new_text = PRXCHANGE('s/apple|APPLE/orange/', -1, text);
PUT "Original: " text;
PUT "New: " new_text;
RUN;
示例2:捕获两个组,颠倒其位置
/* Create a data set that contains a list of names. */
data ReversedNames;
input name & $32.;
datalines;
Jones, Fred
Kavich, Kate
Turley, Ron
Dulix, Yolanda
;
/* Reverse last and first names with a DATA step. */
options pageno=1 nodate ls=80 ps=64;
data names;
set ReversedNames;
name = prxchange('s/(\w+), (\w+)/$2 $1/', -1, name);
run;
proc print data=names;
run;
示例3:改变单词部分的大小写,U指大写、L指小写、E值不变
data _null_;
length txt $32;
txt = prxchange ('s/(big)(black)(bear)/\U$1\L$2\E$3/', 1, 'bigblackbear');
put txt=;
run;
示例4:匹配指定的数字组合,将其替换成PHONE NUMBER REMOVED
options nodate nostimer ls=78 ps=60;
/* Create data set that contains confidential information. */
data a;
input text $80.;
datalines;
The phone number for Ed is (801)443-9876 but not until tonight.
He can be reached at (910)998-8762 tomorrow for testing purposes.
;
run;
/* Locate confidential phone numbers and replace them with message */
/* indicating that they have been removed. */
data b;
set a;
text = prxchange('s/\([2-9]\d\d\) ?[2-9]\d\d-\d\d\d\d/*PHONE NUMBER
REMOVED*/', -1, text);
put text=;
run;
proc print data = b;
run;
PRXNEXT
循环查找字符串中所有匹配项。
语法:call prxnext(pattern_id, start, stop, string, position, length);
示例1:遍历抓取一段字符串里指定的部分
DATA _NULL_;
text = "Contact us at support@example.com or for sales, email sales.team@domain.org.";
/* 模式: 匹配电子邮件地址的通用模式 */
/* \w+[\w\.\-]*@\w+[\w\-]*(\.\w{2,})+ */
/* (\.\w{2,})+ 表示匹配像 .com 或 .co.uk 这样的域名后缀 */
pattern_id = PRXPARSE('/(\w+[\w\.\-]*@\w+[\w\-]*(\.\w{2,})+)/');
start = 1;
stop = LENGTH(text);
CALL PRXNEXT(pattern_id, start, stop, text, position, length);
DO WHILE (position > 0);
/* PRXPOSN的第二个参数为0表示获取整个匹配的子串 */
found_email = SUBSTR(text, position, length);
PUT "找到邮件: " found_email;
/* 继续从上一个匹配结束的位置向后搜索 */
CALL PRXNEXT(pattern_id, start, stop, text, position, length);
END;
RUN;
解释:pattern_id = PRXPARSE('/(\w+[\w\.\-]*@\w+[\w\-]*(\.\w{2,})+)/');,
'//'的括号里面是正则表达式的匹配内容,\w+[\w\.\-]*里\w+指匹配多个连续字符串,[\w\.\-]指匹配多个字符串中夹杂着.和-,这两个符号在语句里都用了\转义;随后用了@符号来匹配邮箱的符号;然后\w+[\w\-]*匹配字母数字;之后是(\.\w{2,})+)来匹配.com或者.co等部分。
示例2:匹配指定单词出现的位置
data _null_;
ExpressionID = prxparse('/[c|r|b]at/');
text = 'The woods have a bat, cat, and a rat!';
start = 1;
stop = length(text);
call prxnext(ExpressionID, start, stop, text, position, length);
do while (position > 0);
found = substr(text, position, length);
put found= position= length=;
call prxnext(ExpressionID, start, stop, text, position, length);
end;
run;
PRXPOSN
获取第n个捕获组(Parentheses Group)的位置和长度。
语法:value = PRXPOSN(pattern_id, group_number, type, string);
type=0:返回长度; type=1:返回起始位置。
示例1:提取指定的字符串部分,提取姓氏
data ReversedNames;
input name & $32.;
datalines;
Jones, Fred
Kavich, Kate
Turley, Ron
Dulix, Yolanda
;
data FirstLastNames;
length first last $ 16;
keep first last;
retain re;
if _N_ = 1 then
re = prxparse('/(\w+), (\w+)/');
set ReversedNames;
if prxmatch(re, name) then
do;
last = prxposn(re, 1, name);
first = prxposn(re, 2, name);
end;
run;
options pageno=1 nodate ls=80 ps=64;
proc print data = FirstLastNames;
run;
示例2:将字符串分组提取,分组提取姓氏
data old;
input name $60.;
datalines;
Judith S Reaveley
Ralph F. Morgan
Jess Ennis
Carol Echols
Kelly Hansen Huff
Judith
Nick
Jones
;
data new;
length first middle last $ 40;
keep first middle last;
re = prxparse('/(\S+)\s+([^\s]+\s+)?(\S+)/o');
set old;
if prxmatch(re, name) then
do;
first = prxposn(re, 1, name);
middle = prxposn(re, 2, name);
last = prxposn(re, 3, name);
output;
end;
run;
练习
光看没用,练习一下:
proc delete data=work._all_; run;quit;
data a1;
test = "(123)456-7890";
run;
data a2;
test = "(123";
run;
data a3;
test = "(123)456-";
run;
data a4;
test = "asdfbasda7523asdfa1adfa324";
run;
data atest;
length test $1000.;
set a:;
run;
/* 问题:
1.检查:检查是否具有指定字符'三位数字加-',如果有标记为flag=Y;
2.提取:提取指定字符到新列;
3.替换:将指定字符替换成指定内容;
4.统计,连续出现指定字符,将其累加并且output成观测行;
5.分列,根据某列内的规律,将其拆分成多列
*/
/* 1.检查:检查是否具有指定字符'三位数字加-',如果有标记为flag=Y */
data b;
set atest;
if prxmatch('/\d{3}\-/',test) then flag="Y";
proc print data=b;
run;
/* 2.提取:提取指定字符到新列 */
data b;
set atest;
pattern_id = prxparse('/\-\d{4}/');
if pattern_id > 0 then do;
call prxsubstr(pattern_id,test,position,length);
if position > 0 then do;
extract_content = substr(test, position, length);
end;
end;
proc print data=b;
run;
/* 3.替换:将指定字符替换成指定内容 */
data b;
set atest;
new_test = prxchange('s/\d{4}/XXXX/', -1, test);
proc print data=b;
run;
/* 4.统计,连续出现指定字符,将其累加并且output成观测行 */
data b;
set atest;
continue_num = prxparse('/\d{3}/');
start = 1;
stop = length(test);
call prxnext(continue_num, start, stop, test, position, length);
n = 0;
do while(position > 0);
found = substr(test, position, length);
if ^missing(found) then do;
n = n + 1;
output;
end;
put found= position= length=;
call prxnext(continue_num, start, stop, test, position, length);
end;
run;
proc print data=b;
run;
/* 5.分列,根据某列内的规律,将其拆分成多列 */
data work.person_info;
length line $50 FirstName LastName $30 AgeStr $10;
/* 示例输入数据 */
input line $;
/* 编译正则表达式 */
re = prxparse('/([^,]+),([^,]+),(\d+)/');
/* 匹配字符串 */
if prxmatch(re, line) then do;
/* 提取各捕获组内容 */
FirstName = prxposn(re, 1, line);
LastName = prxposn(re, 2, line);
AgeStr = prxposn(re, 3, line);
Age = input(AgeStr, best.);
end;
datalines;
"John,Doe,30"
"Jane,Smith,25"
"Michael,Brown,40"
;
run;

