如何从字符串中删除所有非可打印字符?

210

我想我需要删除0-31和127字符。

是否有可以高效完成此操作的函数或代码片段?

18个回答

458

7位ASCII码?

如果您的Tardis刚落地在1963年,而您只需要7位可打印ASCII字符,则可以使用以下代码来去除0至31和127至255之间的所有内容:

$string = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $string);

它匹配范围在0-31、127-255内的任何字符并将其删除。

8位扩展ASCII码?

你回到了80年代。如果你使用的是某种形式的8位ASCII码,那么你可能希望保留范围在128-255的字符。简单调整一下 - 只需查找0-31和127。

$string = preg_replace('/[\x00-\x1F\x7F]/', '', $string);

UTF-8?

欢迎回到21世纪。如果您有一个UTF-8编码的字符串,那么可以在正则表达式上使用/u修饰符。(参考链接)

$string = preg_replace('/[\x00-\x1F\x7F]/u', '', $string);
这只是删除了0-31和127。这在ASCII和UTF-8中都有效,因为两者共享相同的控制字符集范围(如mgutt在下面指出的)。严格来说,在没有/u修饰符的情况下也可以工作。但是如果您想删除其他字符,则使用该修饰符会更加方便...

如果您要处理Unicode,则可能会有多种非打印元素,但让我们考虑一个简单的元素:NO-BREAK SPACE (U+00A0)

在UTF-8字符串中,这将被编码为0xC2A0。您可以查找并删除该特定序列,但是有了放置了/u修饰符,您可以直接将\xA0添加到字符类中:

$string = preg_replace('/[\x00-\x1F\x7F\xA0]/u', '', $string);

附加说明:str_replace有什么用?

preg_replace非常高效,但如果您需要经常进行此操作,您可以创建一个字符数组来移除所需的字符,并使用如mgutt所述的str_replace,例如:

//build an array we can re-use across several operations
$badchar=array(
    // control characters
    chr(0), chr(1), chr(2), chr(3), chr(4), chr(5), chr(6), chr(7), chr(8), chr(9), chr(10),
    chr(11), chr(12), chr(13), chr(14), chr(15), chr(16), chr(17), chr(18), chr(19), chr(20),
    chr(21), chr(22), chr(23), chr(24), chr(25), chr(26), chr(27), chr(28), chr(29), chr(30),
    chr(31),
    // non-printing characters
    chr(127)
);

//replace the unwanted chars
$str2 = str_replace($badchar, '', $str);

直觉上,这似乎会很快,但并非总是如此,您确实应该进行基准测试以查看它是否能为您节省任何东西。我使用随机数据对各种字符串长度进行了一些基准测试,并且在php 7.0.12下出现了这种模式。

     2 chars str_replace     5.3439ms preg_replace     2.9919ms preg_replace is 44.01% faster
     4 chars str_replace     6.0701ms preg_replace     1.4119ms preg_replace is 76.74% faster
     8 chars str_replace     5.8119ms preg_replace     2.0721ms preg_replace is 64.35% faster
    16 chars str_replace     6.0401ms preg_replace     2.1980ms preg_replace is 63.61% faster
    32 chars str_replace     6.0320ms preg_replace     2.6770ms preg_replace is 55.62% faster
    64 chars str_replace     7.4198ms preg_replace     4.4160ms preg_replace is 40.48% faster
   128 chars str_replace    12.7239ms preg_replace     7.5412ms preg_replace is 40.73% faster
   256 chars str_replace    19.8820ms preg_replace    17.1330ms preg_replace is 13.83% faster
   512 chars str_replace    34.3399ms preg_replace    34.0221ms preg_replace is  0.93% faster
  1024 chars str_replace    57.1141ms preg_replace    67.0300ms str_replace  is 14.79% faster
  2048 chars str_replace    94.7111ms preg_replace   123.3189ms str_replace  is 23.20% faster
  4096 chars str_replace   227.7029ms preg_replace   258.3771ms str_replace  is 11.87% faster
  8192 chars str_replace   506.3410ms preg_replace   555.6269ms str_replace  is  8.87% faster
 16384 chars str_replace  1116.8811ms preg_replace  1098.0589ms preg_replace is  1.69% faster
 32768 chars str_replace  2299.3128ms preg_replace  2222.8632ms preg_replace is  3.32% faster

这些时间是针对10000次迭代的,但更有趣的是相对差异。在512个字符以下,我看到preg_replace总是获胜。在1-8kb范围内,str_replace略有优势。

我认为这是一个有趣的结果,所以在这里包含它。重要的不是拿这个结果来决定使用哪种方法,而是根据你自己的数据进行基准测试,然后再决定。


15
如果您需要将换行符视为安全字符,请将表达式更改为以下内容(查找可打印字符的反向匹配):preg_replace(/[^\x0A\x20-\x7E]/,'',$string); - Nick
13
“UTF-8字符”这个说法是不存在的。存在的是Unicode符号/字符,而UTF-8是一种编码方式,可以表示所有Unicode字符。你的意思是对ASCII字符集之外的字符不起作用。 - Mathias Bynens
3
如果您需要匹配一个 Unicode 字符大于 \xFF,可以使用 \x{####}。 - Peter Olson
2
这将删除阿拉伯字母,不是好的解决方案。 - Ayman Hussein
1
是一种编码,而不是一个字符。上面的解决方案仅适用于ASCII字符。 - Paul Dixon
显示剩余13条评论

160

这里的许多其他答案没有考虑到 Unicode 字符(例如 öäüßйȝîûηыეமிᚉ⠛)。在这种情况下,可以使用以下代码:

$string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/u', '', $string);

\x80-\x9F 范围内(七位 ASCII 字符范围之上),有一类奇怪的字符,它们从技术上来说是控制字符,但随着时间的推移,已经被错误地用作可打印字符。如果您对这些字符没有任何问题,那么可以使用以下内容:

$string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $string);

如果你希望同时去掉换行符、回车符、制表符、非断空格和软连字符,可以使用以下代码:

$string = preg_replace('/[\x00-\x1F\x7F-\xA0\xAD]/u', '', $string);

请注意,上述示例中您必须使用单引号。

如果您希望剥离除基本可打印ASCII字符之外的所有内容(上述所有示例字符都将被剥离),您可以使用:

$string = preg_replace('/[^[:print:]]/', '', $string);

参考链接:http://www.fileformat.info/info/charset/UTF-8/list.htm


1
你的正则表达式能够很好地处理UTF8字符,但是它会去掉非UTF8的“特殊”字符,如ç、ü和ö。'/[\x00-\x1F\x80-\xC0]/u'保留了它们的完整性,但也包括了除法(F7)和乘法(D7)符号。 - Hazard
1
@Hazar 是的,你说得对,\x80-\xFF 过滤掉了太多内容,但是 \x80-\xC0 仍然过于严格。这会错过其他可打印字符,例如 ©£±。参考 http://www.utf8-chartable.de/。 - Dalin
1
@TimMalone 因为 PHP 将展开这些字符序列: http://php.net/manual/zh/language.types.string.php#language.types.string.syntax.double 所以正则表达式将无法看到你试图告诉它的范围。 - Dalin
1
7F怎么办?它不应该是\x7F-\x9F吗? - Bell
1
我刚刚尝试了很多,我尝试了PHP中可用的所有编码函数,从正则表达式到mb_再到htmlspecialchars等等。没有一种方法可以去除控制字符,感谢您的付出。 - John
显示剩余9条评论

48

自PHP 5.2版本开始,我们也可以使用filter_var函数来过滤非打印字符(小于32和大于127的ASCII字符),但我没有看到任何提及,所以我想在这里提一下。您可以使用以下代码使用filter_var过滤非打印字符(小于32和大于127的ASCII字符):

过滤小于32的ASCII字符

$string = filter_var($input, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);

过滤掉ASCII字符编码大于127的字符

$string = filter_var($input, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_HIGH);

去除两端空白:

$string = filter_var($input, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW|FILTER_FLAG_STRIP_HIGH);

您还可以对低字符(换行符、制表符等)进行HTML编码,同时剥离高字符:

$string = filter_var($input, FILTER_UNSAFE_RAW, FILTER_FLAG_ENCODE_LOW|FILTER_FLAG_STRIP_HIGH);

还有一些去除HTML标签、清理电子邮件和URL等的选项。因此,有很多用于净化(删除数据)甚至验证(如果不合法则返回false而不是静默删除)的选项。

净化:http://php.net/manual/en/filter.filters.sanitize.php

验证:http://php.net/manual/en/filter.filters.validate.php

但是,仍然存在问题,即FILTER_FLAG_STRIP_LOW将剥离换行符和回车符,而对于文本区域来说,这些字符完全是有效的... 所以一些正则表达式的答案在某些情况下仍然是必要的,例如,在检查了这个线程之后,我打算为文本区域执行以下操作:

$string = preg_replace( '/[^[:print:]\r\n]/', '',$input);

这个看起来比一些通过数值范围剥离的正则表达式更易读。


其他答案对我没用,"filter_var()"的解决方案完美地解决了我的问题。感谢7年后! :) - VG-Electronics

27

你可以使用字符类

/[[:cntrl:]]+/

1
这需要我使用ereg吗? - Stewart Robinson

23

所有的解决方案都只是部分有效,甚至下面的解决方案可能也不能涵盖所有情况。我的问题是尝试将一个字符串插入到一个utf8 mysql表中。该字符串(及其字节)都符合utf8,但有几个错误序列。我猜想其中大部分是控制或格式化字符。

function clean_string($string) {
  $s = trim($string);
  $s = iconv("UTF-8", "UTF-8//IGNORE", $s); // drop all non utf-8 characters

  // this is some bad utf-8 byte sequence that makes mysql complain - control and formatting i think
  $s = preg_replace('/(?>[\x00-\x1F]|\xC2[\x80-\x9F]|\xE2[\x80-\x8F]{2}|\xE2\x80[\xA4-\xA8]|\xE2\x81[\x9F-\xAF])/', ' ', $s);

  $s = preg_replace('/\s+/', ' ', $s); // reduce all multiple whitespace to a single space

  return $s;
}

为了进一步加剧问题,涉及到内容的表格、服务器、连接和渲染如此处稍微讨论


2
通过了我所有的单元测试,太棒了! - Korri
\xE2\x80[\xA4-\xA8](或226.128.[164-168])是错误的,该序列包括以下可打印符号:Unicode字符“ONE DOT LEADER”(U+2024),Unicode字符“TWO DOT LEADER”(U+2025),Unicode字符“HORIZONTAL ELLIPSIS”(U+2026),Unicode字符“HYPHENATION POINT”(U+2027)。并且只有一个不可打印符号:Unicode字符“LINE SEPARATOR”(U+2028)。下一个也是不可打印的:Unicode字符“PARAGRAPH SEPARATOR”(U+2029)。因此,请使用\xE2\x80[\xA8-\xA9]替换该序列,以删除LINE SEPARATOR和PARAGRAPH SEPARATOR。 - MingalevME
2
这是我目前找到的最佳解决方案,但由于所有表情符号都会破坏MySQL,所以我还不得不添加$s = preg_replace('/(\xF0\x9F[\x00-\xFF][\x00-\xFF])/', ' ', $s); - Joe Black
很不幸,上述“坏的utf-8”正则表达式也会删除换行符! - Avatar

17
这更简单:

$string = preg_replace('/[^[:cntrl:]]/', '',$string);

这段代码是用于将字符串中非控制字符的地方替换为空格。

5
这也会去除换行符、回车符和UTF8字符。 - Dalin
6
“UTF-8字符”这个概念并不存在。存在的是Unicode符号/字符,而UTF-8是一种编码方式,可以表示所有Unicode符号/字符。你本意是说这会删除ASCII范围之外的字符。 - Mathias Bynens
2
吃掉阿拉伯字符 :) - Rolf

15

从输入字符串中删除所有非ASCII字符

$result = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $string);

该代码移除十六进制范围在0-31和128-255之间的任何字符,只留下结果字符串中十六进制32-127的字符,在这个例子中称为$result。


为什么我想要127,它是DEL?与其删除128到255而不是127到255,使用[\x00-\x1F\x7F-\xFF]是否更好? - Volomike

13

对于UTF-8,可以尝试以下代码:

preg_replace('/[^\p{L}\s]/u','', $string);

这是我10年前的原始答案,正如评论所说,它非常适合用于全文搜索引擎,因为它会移除一些非文本可打印字符,例如[]!~等。

如果你还需要删除无效字符以供 libexpat(叹气)使用,可以尝试以下代码:

preg_replace('/[^\PCc^\PCn^\PCs]/u', '', $string);

有关该方法的详细信息,请参见此答案


10
这将删除引号、括号等字符。这些字符确实可以打印。 - Gajus
这太棒了!它救了我的命,打印阿拉伯字符时出了问题,运行得像冠军 :) - krishna
当只需要纯文本时,这将非常有用。例如,在页面上进行搜索引擎和数据库索引时。此时括号、句号和逗号都是不必要的。 - Robert

10
您可以使用正则表达式来除去除了您想要保留的字符以外的所有内容:
$string=preg_replace('/[^A-Za-z0-9 _\-\+\&]/','',$string);

使用正则表达式替换除字母A-Z或a-z、数字0-9、空格、下划线、连字符、加号和&符号以外的所有字符为空(即删除)。


6

1
它对我来说完美运作!我只是为UTF-8字符添加了/u。您能否解释一下第一部分(?!\n)的作用? - Marcio Mazzucato
太好了!我正在寻找一种方法来删除Unicode中的“无用”字符并保留重要字符(包括带重音的字母、数字和特殊字符)。感谢您的答案和文档链接。 - azerto00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接