PHP: Como remover todos os caracteres não imprimíveis em uma string?

159

Eu imagino que preciso remover os caracteres 0-31 e 127,

Existe uma função ou parte do código para fazer isso com eficiência.

Stewart Robinson
fonte

Respostas:

354

ASCII de 7 bits?

Se a sua Tardis acabou de chegar em 1963 e você deseja apenas os caracteres ASCII imprimíveis de 7 bits, pode extrair tudo de 0 a 31 e 127 a 255 com isso:

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

Corresponde a qualquer coisa no intervalo de 0 a 31, 127 a 255 e o remove.

ASCII estendido de 8 bits?

Você caiu em uma máquina do tempo de banheira de hidromassagem e voltou aos anos oitenta. Se você possui algum tipo de ASCII de 8 bits, convém manter os caracteres no intervalo 128-255. Um ajuste fácil - basta procurar 0-31 e 127

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

UTF-8?

Bem-vindo de volta ao século XXI. Se você tiver uma string codificada em UTF-8, o /u modificador poderá ser usado na regex

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

Isso apenas remove 0-31 e 127. Isso funciona em ASCII e UTF-8 porque ambos compartilham o mesmo intervalo de conjunto de controle (conforme observado por mgutt abaixo). A rigor, isso funcionaria sem o /umodificador. Mas facilita a vida se você deseja remover outros caracteres ...

Se você estiver lidando com Unicode, há potencialmente muitos elementos que não são de impressão , mas vamos considerar um simples: NO-BREAK SPACE (U + 00A0)

Em uma sequência UTF-8, isso seria codificado como 0xC2A0. Você pode procurar e remover essa sequência específica, mas com o /umodificador instalado, você pode simplesmente adicionar \xA0à classe de caracteres:

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

Adendo: E o str_replace?

preg_replace é bastante eficiente, mas se você estiver fazendo muito esta operação, poderá criar uma matriz de caracteres que deseja remover e usar str_replace conforme observado por mgutt abaixo, por exemplo

//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);

Intuitivamente, parece que seria rápido, mas nem sempre é o caso; você deve definitivamente fazer um benchmark para ver se isso poupa alguma coisa. Eu fiz alguns benchmarks em vários comprimentos de string com dados aleatórios, e esse padrão surgiu usando o 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

Os horários são para 10000 iterações, mas o mais interessante são as diferenças relativas. Até 512 caracteres, eu estava vendo preg_replace sempre ganhar. No intervalo de 1-8kb, str_replace tinha uma margem marginal.

Eu pensei que era um resultado interessante, então incluí-lo aqui. O importante não é pegar esse resultado e usá-lo para decidir qual método usar, mas para comparar seus próprios dados e depois decidir.

Paul Dixon
fonte
14
Se você precisar considerar uma nova linha segura, altere a expressão para esta (procure inversamente por imprimíveis): preg_replace (/ [^ \ x0A \ x20- \ x7E] /, '', $ string);
Nick
12
@ Dalin Não existe um "caractere UTF-8". Existem símbolos / caracteres Unicode, e UTF-8 é uma codificação que pode representar todos eles. Você quis dizer que isso não funciona para caracteres fora do conjunto de caracteres ASCII.
Mathias Bynens
3
Se você precisa combinar um caractere Unicode acima \ xFF, use \ x {####}
Peter Olson
você perdeu \ x7F (127), que é um personagem não-imprimíveis
Mubashar
isso removerá as letras árabes, solução ruim.
Ayman Hussein 27/03
141

Muitas das outras respostas aqui não levam em consideração caracteres unicode (por exemplo, öäüßйȝîûηы ე மி ᚉ ⠛). Nesse caso, você pode usar o seguinte:

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

Há uma classe estranha de caracteres no intervalo \x80-\x9F(logo acima do intervalo ASCII de 7 bits) que são tecnicamente caracteres de controle, mas com o tempo foram utilizados incorretamente para caracteres imprimíveis. Se você não tiver nenhum problema com isso, poderá usar:

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

Se você também deseja remover faixas de alimentação de linha, retornos de carro, guias, espaços sem quebra e hífens suaves, você pode usar:

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

Observe que você deve usar aspas simples para os exemplos acima.

Se você deseja remover tudo, exceto caracteres ASCII básicos imprimíveis (todos os caracteres de exemplo acima serão removidos), você pode usar:

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

Para referência, consulte http://www.fileformat.info/info/charset/UTF-8/list.htm

Dalin
fonte
1
Seu regexp lida bem com caracteres UTF8; mas remove caracteres "especiais" não UTF8; como ç, ü e ö. '/[\x00-\x1F\x80-\xC0]/u'os deixa intactos; mas também sinal de divisão (F7) e multiplicação (D7).
Hazar
@Hazar sim, você está correto \ x80- \ xFF retirado demais, mas \ x80- \ xC0 ainda é muito restritivo. Faltariam outros caracteres imprimíveis como © £ ±. Para referência, consulte utf8-chartable.de
Dalin
1
@ TimMalone, porque o PHP expandirá essas seqüências de caracteres: php.net/manual/en/… para que o regex não veja o intervalo que você está tentando falar.
Dalin
1
E o 7F? Não deveria ser \x7F-\x9F?
Sino
1
Eu apenas tentei bastante, tentei todas as funções de codificação disponíveis no PHP, do regex ao mb_, ao htmlspecialchars etc. Nada removeu os caracteres de controle, obrigado por investir no trabalho.
João
29

A partir do PHP 5.2, também temos acesso ao filter_var, que eu não vi nenhuma menção, então pensei em lançá-lo lá fora. Para usar filter_var para remover caracteres não imprimíveis <32 e> 127, você pode:

Filtrar caracteres ASCII abaixo de 32

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

Filtrar caracteres ASCII acima de 127

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

Retire ambos:

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

Você também pode codificar caracteres baixos em html (nova linha, guia etc.) enquanto retira o conteúdo alto:

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

Também existem opções para remover HTML, higienizar e-mails e URLs, etc. Portanto, muitas opções para higienização (remover dados) e até validação (retornam false se não for válido em vez de remover silenciosamente).

Sanitização: http://php.net/manual/en/filter.filters.sanitize.php

Validação: http://php.net/manual/en/filter.filters.validate.php

No entanto, ainda existe o problema, que o FILTER_FLAG_STRIP_LOW retira novas linhas e retornos de carro, que para uma área de texto são caracteres completamente válidos ... portanto, algumas respostas do Regex, eu acho, ainda são necessárias às vezes, por exemplo, depois de revisar isso thread, pretendo fazer isso para textareas:

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

Isso parece mais legível do que um número de expressões regulares removidas por intervalo numérico.

Kevin Nelson
fonte
27

você pode usar classes de caracteres

/[[:cntrl:]]+/
ghostdog74
fonte
isso não exige que eu use ereg embora?
Stewart Robinson
18

isso é mais simples:

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

jacktrade
fonte
5
Isso também remove feeds de linha, retornos de carro e caracteres UTF8.
Dalin
5
@ Dalin Não existe um "caractere UTF-8". Existem símbolos / caracteres Unicode, e UTF-8 é uma codificação que pode representar todos eles. Você queria dizer que isso também remove caracteres fora do intervalo ASCII .
Mathias Bynens
1
Come-se caracteres árabes :)
Rolf
16

Todas as soluções funcionam parcialmente, e mesmo abaixo provavelmente não cobre todos os casos. Meu problema foi ao tentar inserir uma string em uma tabela mysql utf8. A string (e seus bytes) todos estavam em conformidade com utf8, mas tinham várias seqüências incorretas. Presumo que a maioria deles tenha controle ou formatação.

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;
}

Para agravar ainda mais o problema é a tabela x servidor x conexão x renderização do conteúdo, como discutido aqui um pouco

Wayne Weibel
fonte
1
O único que passa em todos os meus testes de unidade, incrível!
Korri
\ xE2 \ x80 [\ xA4- \ xA8] (ou 226.128. [164-168]) - está incorreta, a sequência inclui os seguintes símbolos imprimíveis: Caractere Unicode 'ONE DOT LEADER' (U + 2024), Unicode 'TWO DOT LÍDER '(U + 2025), caractere Unicode' ELLIPSIS HORIZONTAL '(U + 2026), caractere Unicode' PONTO DE HIFENAÇÃO '(U + 2027). E apenas um não imprimível: caractere Unicode 'LINE SEPARATOR' (U + 2028). O próximo também não é imprimível: Caractere Unicode 'PARAGRAPH SEPARATOR' (U + 2029). Portanto, substitua a sequência por: \ xE2 \ x80 [\ xA8- \ xA9] \ xE2 \ x80 [\ xA8- \ xA9] para remover LINE SEPARATOR e PARAGRAPH SEPARATOR.
MingalevME
Esta é a melhor solução que eu poderia encontrar até agora, mas eu laso teve que adicionar $s = preg_replace('/(\xF0\x9F[\x00-\xFF][\x00-\xFF])/', ' ', $s);causa de todos os caracteres emoji estavam atrapalhando mysql
Joe Black
9

Minha versão compatível com UTF-8:

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

cedivad
fonte
7
Isso remove caracteres como aspas, colchetes etc. Esses certamente são caracteres imprimíveis.
Gajus
isso é maravilhoso! ele salvou a minha vida, confuso durante a impressão de caracteres árabes, trabalhou como campeão :)
krishna
6

Você pode usar um expresso regular para remover tudo, exceto os caracteres que deseja manter:

$string=preg_replace('/[^A-Za-z0-9 _\-\+\&]/','',$string);

Substitui tudo o que não é (^) as letras AZ ou az, os números de 0 a 9, espaço, sublinhado, hífen, mais e comercial - por nada (por exemplo, remova-o).

Richy B.
fonte
5
preg_replace('/(?!\n)[\p{Cc}]/', '', $response);

Isso removerá todos os caracteres de controle ( http://uk.php.net/manual/en/regexp.reference.unicode.php ) deixando os \ncaracteres de nova linha. Pela minha experiência, os caracteres de controle são os que mais frequentemente causam problemas de impressão.

Gajus
fonte
1
Funciona perfeito para mim! Eu adicionei apenas /upara caracteres UTF-8. Você poderia explicar o que a primeira parte (?!\n)faz?
Marcio Mazzucato
4

Para retirar todos os caracteres não ASCII da sequência de entrada

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

Esse código remove qualquer caractere nos intervalos hexadecimais 0-31 e 128-255, deixando apenas os caracteres hexadecimais 32-127 na cadeia resultante, que eu chamo de $ result neste exemplo.

Junaid Masood
fonte
3

A resposta de @PaulDixon está completamente errada , porque remove os caracteres ASCII estendidos imprimíveis 128-255! foi parcialmente corrigido. Não sei por que ele ainda deseja excluir 128-255 de um conjunto ASCII de 7 bits e 127 caracteres, pois não possui os caracteres ASCII estendidos.

Mas, finalmente, era importante não excluir 128-255 porque, por exemplo chr(128)( \x80), o sinal de euro é ASCII de 8 bits e muitas fontes UTF-8 no Windows exibem um sinal de euro e o Android em relação ao meu próprio teste.

E ele matará muitos caracteres UTF-8 se você remover os caracteres ASCII 128-255 de uma string UTF-8 (provavelmente os bytes iniciais de um caractere UTF-8 de vários bytes). Então não faça isso! Eles são caracteres completamente legais em todos os sistemas de arquivos usados ​​atualmente. O único intervalo reservado é de 0 a 31 .

Em vez disso, use isso para excluir os caracteres não imprimíveis 0-31 e 127:

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

Ele funciona em ASCII e UTF-8 porque ambos compartilham o mesmo intervalo de conjunto de controle .

A alternativa mais lenta¹ mais rápida sem usar expressões regulares:

$string = str_replace(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)
), '', $string);

Se você quiser manter todos os espaços em branco \t, \ne \r, em seguida, remover chr(9), chr(10)e chr(13)a partir dessa lista. Nota: O espaço em branco usual é chr(32)para que permaneça no resultado. Decida-se se você deseja remover o espaço sem quebra, chr(160)pois isso pode causar problemas.

¹ Testado por @PaulDixon e verificado por mim mesmo.

mgutt
fonte
2

e quanto a:

return preg_replace("/[^a-zA-Z0-9`_.,;@#%~'\"\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:\-\s\\\\]+/", "", $data);

me dá controle total do que eu quero incluir

sdfor
fonte
0

O anwser marcado é perfeito, mas perde o caractere 127 (DEL), que também é um caractere não imprimível

minha resposta seria

$string = preg_replace('/[\x00-\x1F\x7f-\xFF]/', '', $string);
Mubashar
fonte
Esta resposta também está errada. Veja: stackoverflow.com/a/42058165/318765
mgutt
a resposta acima foi um elogio à resposta original, que apenas adiciona o caractere "excluir".
Mubashar
0

"cedivad" resolveu o problema para mim com resultado persistente dos caracteres suecos ÅÄÖ.

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

Obrigado!

Andreas Ek
fonte
0

Para quem ainda está procurando como fazer isso sem remover os caracteres não imprimíveis, mas escapando deles, fiz isso para ajudar. Sinta-se livre para melhorá-lo! Os caracteres são escapados para \\ x [A-F0-9] [A-F0-9].

Ligue assim:

$escaped = EscapeNonASCII($string);

$unescaped = UnescapeNonASCII($string);

<?php 
  function EscapeNonASCII($string) //Convert string to hex, replace non-printable chars with escaped hex
    {
        $hexbytes = strtoupper(bin2hex($string));
        $i = 0;
        while ($i < strlen($hexbytes))
        {
            $hexpair = substr($hexbytes, $i, 2);
            $decimal = hexdec($hexpair);
            if ($decimal < 32 || $decimal > 126)
            {
                $top = substr($hexbytes, 0, $i);
                $escaped = EscapeHex($hexpair);
                $bottom = substr($hexbytes, $i + 2);
                $hexbytes = $top . $escaped . $bottom;
                $i += 8;
            }
            $i += 2;
        }
        $string = hex2bin($hexbytes);
        return $string;
    }
    function EscapeHex($string) //Helper function for EscapeNonASCII()
    {
        $x = "5C5C78"; //\x
        $topnibble = bin2hex($string[0]); //Convert top nibble to hex
        $bottomnibble = bin2hex($string[1]); //Convert bottom nibble to hex
        $escaped = $x . $topnibble . $bottomnibble; //Concatenate escape sequence "\x" with top and bottom nibble
        return $escaped;
    }

    function UnescapeNonASCII($string) //Convert string to hex, replace escaped hex with actual hex.
    {
        $stringtohex = bin2hex($string);
        $stringtohex = preg_replace_callback('/5c5c78([a-fA-F0-9]{4})/', function ($m) { 
            return hex2bin($m[1]);
        }, $stringtohex);
        return hex2bin(strtoupper($stringtohex));
    }
?>
DropItLikeItsHot
fonte
0

Resolvi o problema para o UTF8 usando https://github.com/neitanod/forceutf8

use ForceUTF8\Encoding;

$string = Encoding::fixUTF8($string);
mnv
fonte
1
Essa lib converte caracteres acentuados UTF-8 e emoticons UTF-8 em "?" símbolos Problema bastante sério, infelizmente.
ChristoKiwi
0

O regex na resposta selecionada falha para Unicode: 0x1d (com php 7.4)

uma solução:

<?php
        $ct = 'différents'."\r\n test";

        // fail for Unicode: 0x1d
        $ct = preg_replace('/[\x00-\x1F\x7F]$/u', '',$ct);

        // work for Unicode: 0x1d
        $ct =  preg_replace( '/[^\P{C}]+/u', "",  $ct);

        // work for Unicode: 0x1d and allow line break
        $ct =  preg_replace( '/[^\P{C}\n]+/u', "",  $ct);

        echo $ct;

from: UTF 8 String remove todos os caracteres invisíveis, exceto a nova linha

Mkdgs
fonte