大数跨境
0
0

SAS正则表达式笔记

SAS正则表达式笔记 Lily说跨境
2025-10-11
7
导读:SAS通过PRX系列函数(如PRXPARSE、PRXMATCH)支持正则表达式,用于高效匹配、查找、提取和替换

SAS通过PRX系列函数(如PRXPARSE、PRXMATCH)支持正则表达式,用于高效匹配、查找、提取和替换文本模式,构成正则表达式语句的主要结构是元素和函数。

常用元素

构成正则表达式的语句有很多元素:

  • .
    (点): 匹配除换行符外的任意单个字符。
  • \d
    : 匹配任意一个数字 (等同于 [0-9])。
  • \w
    : 匹配任意一个单词字符 (字母、数字或下划线)。
  • \s
    : 匹配任意一个空白字符 (空格、制表符等)。
  • ^
    : 匹配字符串的开头。
  • $
    : 匹配字符串的结尾。
  • […]
    : 匹配方括号中包含的任意一个字符。例如 [abc] 匹配 "a", "b", 或 "c"。
  • (…)
    : 将模式分组,作为一个单元使用。
  • *
    : 匹配前面的元素零次或多次。
  • +
    : 匹配前面的元素一次或多次。
  • ?
    : 匹配前面的元素零次或一次。
  • |
    : 或逻辑操作符。例如 a|b 匹配 "a" 或 "b"。

这些元素可以不用刻意记,在后面的代码中边练边记、边用边记就好。

常用函数

除了上述需要了解的基本元素,我们还需要知道SAS中几个常用的正则表达式相关函数:

PRXPARSE

解析表达式用的,一般认为是执行后续的第一步,程序里常用prxparse(pattern)构建一个语句,再放在其他函数里用。实际上直接写语句不用prxparse也是可以的。

写正则表达式的步骤:

  1. 先写一个冒号:''
  2. 在冒号里面加两个斜杠:'//'
  3. 接下来在斜杠里面输入需要匹配的模式即可
  4. 两个//是需要匹配的内容,如果涉及符号、数字等就用【\】来进行转译。

比如匹配多个数字就是:'/\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, startstoptextpositionlength);
      do while (position > 0);
         found = substr(text, position, length);
         put found= position= length=;
         call prxnext(ExpressionID, startstoptextpositionlength);
      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, namethen 
      do;
         last = prxposn(re, 1name);
         first = prxposn(re, 2name);
      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, namethen
      do;
         first = prxposn(re, 1name);
         middle = prxposn(re, 2name);
         last = prxposn(re, 3name);
         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}\-/',testthen 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;

【声明】内容源于网络
0
0
Lily说跨境
跨境分享库 | 每天一点跨境干货
内容 46933
粉丝 2
Lily说跨境 跨境分享库 | 每天一点跨境干货
总阅读257.2k
粉丝2
内容46.9k