PHP: ¿Cómo eliminar todos los caracteres no imprimibles en una cadena?

161

Me imagino que necesito eliminar los caracteres 0-31 y 127,

¿Existe una función o código para hacer esto de manera eficiente?

Stewart Robinson
fuente

Respuestas:

355

ASCII de 7 bits?

Si su Tardis acaba de aterrizar en 1963, y solo desea los caracteres ASCII imprimibles de 7 bits, puede extraer todo de 0-31 y 127-255 con esto:

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

Coincide con cualquier cosa en el rango 0-31, 127-255 y lo elimina.

ASCII extendido de 8 bits?

Te caíste en una máquina del tiempo del jacuzzi y volviste a los ochenta. Si tiene alguna forma de ASCII de 8 bits, entonces es posible que desee mantener los caracteres en el rango 128-255. Un ajuste fácil: solo busque 0-31 y 127

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

UTF-8?

Ah, bienvenido de nuevo al siglo XXI. Si tiene una cadena codificada UTF-8, entonces el /u modificador puede usarse en la expresión regular

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

Esto simplemente elimina 0-31 y 127. Esto funciona en ASCII y UTF-8 porque ambos comparten el mismo rango de conjunto de control (como se indica en mgutt a continuación). Estrictamente hablando, esto funcionaría sin el /umodificador. Pero hace la vida más fácil si quieres eliminar otros caracteres ...

Si se trata de Unicode, hay muchos elementos que no se pueden imprimir , pero consideremos uno simple: ESPACIO SIN INTERRUPCIONES (U + 00A0)

En una cadena UTF-8, esto se codificaría como 0xC2A0. Puede buscar y eliminar esa secuencia específica, pero con el /umodificador en su lugar, simplemente puede agregar \xA0a la clase de caracteres:

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

Anexo: ¿Qué pasa con str_replace?

preg_replace es bastante eficiente, pero si está haciendo esta operación mucho, podría crear una serie de caracteres que desea eliminar y usar str_replace como se indica en mgutt a continuación, por ejemplo

//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, esto parece que sería rápido, pero no siempre es el caso, definitivamente debes compararlo para ver si te ahorra algo. Hice algunos puntos de referencia en una variedad de longitudes de cadena con datos aleatorios, y este patrón surgió usando 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

Los tiempos en sí son para 10000 iteraciones, pero lo que es más interesante son las diferencias relativas. Hasta 512 caracteres, estaba viendo preg_replace ganar siempre. En el rango de 1-8kb, str_replace tenía un borde marginal.

Pensé que era un resultado interesante, así que incluirlo aquí. Lo importante no es tomar este resultado y usarlo para decidir qué método usar, sino compararlo con sus propios datos y luego decidir.

Paul Dixon
fuente
14
Si necesita considerar una nueva línea segura, cambie la expresión a esto (busque inversamente los imprimibles): preg_replace (/ [^ \ x0A \ x20- \ x7E] /, '', $ string);
Nick
12
@Dalin No hay tal cosa como un "personaje UTF-8". Hay símbolos / caracteres Unicode, y UTF-8 es una codificación que puede representarlos a todos. Querías decir que esto no funciona para caracteres fuera del conjunto de caracteres ASCII.
Mathias Bynens
3
Si necesita hacer coincidir un carácter unicode sobre \ xFF, use \ x {####}
Peter Olson
te perdiste \ x7F (127) que es un personaje no imprimible
Mubashar
Esto eliminará las letras árabes, mala solución.
Ayman Hussein
141

Muchas de las otras respuestas aquí no tienen en cuenta los caracteres unicode (por ejemplo, öäüßйȝîûηы ე மி ᚉ ⠛). En este caso puede usar lo siguiente:

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

Hay una extraña clase de caracteres en el rango \x80-\x9F(justo por encima del rango de caracteres ASCII de 7 bits) que técnicamente son caracteres de control, pero que con el tiempo se han utilizado incorrectamente para caracteres imprimibles. Si no tiene ningún problema con estos, puede usar:

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

Si desea eliminar también los avances de línea, los retornos de carro, las pestañas, los espacios que no se rompen y los guiones suaves, puede usar:

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

Tenga en cuenta que debe usar comillas simples para los ejemplos anteriores.

Si desea despojar todo, excepto los caracteres ASCII imprimibles básicos (se eliminarán todos los caracteres de ejemplo anteriores), puede usar:

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

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

Dalin
fuente
1
Su expresión regular maneja bien los caracteres UTF8; pero elimina los caracteres "especiales" no UTF8; como ç, ü y ö. '/[\x00-\x1F\x80-\xC0]/u'los deja intactos; pero también el signo de división (F7) y multiplicación (D7).
Hazar
@Hazar sí, tienes razón \ x80- \ xFF despojado demasiado, pero \ x80- \ xC0 sigue siendo demasiado restrictivo. Esto echaría de menos otros caracteres imprimibles como © £ ±. Para referencia ver utf8-chartable.de
Dalin
1
@TimMalone porque PHP expandirá esas secuencias de caracteres: php.net/manual/en/… para que la expresión regular no vea el rango del que estás tratando de contarlo.
Dalin
1
¿Qué hay de 7F? ¿No debería ser \x7F-\x9F?
Bell
1
Intenté mucho, probé todas las funciones de codificación disponibles en PHP desde regex a mb_ a htmlspecialchars, etc. Nada eliminó los caracteres de control, gracias por invertir el trabajo.
John
29

Comenzando con PHP 5.2, también tenemos acceso a filter_var, del cual no he visto ninguna mención, así que pensé en lanzarlo allí. Para usar filter_var para quitar caracteres no imprimibles <32 y> 127, puede hacer lo siguiente:

Filtrar caracteres ASCII por debajo de 32

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

Filtrar caracteres ASCII por encima de 127

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

Tira ambos:

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

También puede codificar html con caracteres bajos (nueva línea, tabulación, etc.) mientras se despoja en alto:

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

También hay opciones para eliminar HTML, desinfectar correos electrónicos y URL, etc. Por lo tanto, hay muchas opciones para desinfectar (eliminar datos) e incluso validar (devolver falso si no es válido en lugar de eliminar de forma silenciosa).

Desinfección: http://php.net/manual/en/filter.filters.sanitize.php

Validación: http://php.net/manual/en/filter.filters.validate.php

Sin embargo, todavía existe el problema, que FILTER_FLAG_STRIP_LOW eliminará los retornos de línea y carro, que para un área de texto son caracteres completamente válidos ... así que algunas de las respuestas de Regex, supongo, todavía son necesarias a veces, por ejemplo, después de revisar esto hilo, planeo hacer esto para textareas:

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

Esto parece más legible que una serie de expresiones regulares que se eliminan por rango numérico.

Kevin Nelson
fuente
27

puedes usar clases de personajes

/[[:cntrl:]]+/
ghostdog74
fuente
¿No requiere esto que use ereg?
Stewart Robinson el
18

esto es más simple:

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

Jacktrade
fuente
55
Esto también elimina los avances de línea, los retornos de carro y los caracteres UTF8.
Dalin
55
@Dalin No hay tal cosa como un "personaje UTF-8". Hay símbolos / caracteres Unicode, y UTF-8 es una codificación que puede representarlos a todos. Querías decir que esto también elimina caracteres fuera del rango ASCII .
Mathias Bynens
1
Come caracteres árabes :)
Rolf
16

Todas las soluciones funcionan parcialmente, e incluso a continuación probablemente no cubran todos los casos. Mi problema fue intentar insertar una cadena en una tabla utf8 mysql. La cadena (y sus bytes) se ajustaban a utf8, pero tenían varias secuencias malas. Supongo que la mayoría de ellos eran control o formateo.

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 exacerbar aún más el problema es la tabla vs. servidor vs. conexión vs. representación del contenido, como se mencionó un poco aquí.

Wayne Weibel
fuente
1
El único que pasa todas las pruebas de mi unidad, ¡increíble!
Korri
\ xE2 \ x80 [\ xA4- \ xA8] (o 226.128. [164-168]) - está mal, la secuencia incluye los siguientes símbolos imprimibles: Carácter Unicode 'ONE DOT LEADER' (U + 2024), Carácter Unicode 'TWO DOT LÍDER '(U + 2025), Carácter Unicode' ELIPSIS HORIZONTAL '(U + 2026), Carácter Unicode' PUNTO DE HIPENACIÓN '(U + 2027). Y solo uno no imprimible: Unicode Character 'LINE SEPARATOR' (U + 2028). El siguiente tampoco es imprimible: el carácter Unicode 'PARAGRAPH SEPARATOR' (U + 2029). Entonces reemplace la secuencia con: \ xE2 \ x80 [\ xA8- \ xA9] \ xE2 \ x80 [\ xA8- \ xA9] para eliminar LINE SEPARATOR y PARAGRAPH SEPARATOR.
MingalevME
Esta es la mejor solución que pude encontrar hasta ahora, pero tuve que agregar $s = preg_replace('/(\xF0\x9F[\x00-\xFF][\x00-\xFF])/', ' ', $s);porque todos los caracteres emoji estaban estropeando mysql
Joe Black
10

Mi versión compatible con UTF-8:

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

cedivad
fuente
77
Esto elimina caracteres como comillas, corchetes, etc. Esos son ciertamente caracteres imprimibles.
Gajus
¡esto es maravilloso! me salvó la vida, me equivoqué al imprimir caracteres árabes, funcionó como campeón :)
Krishna
6

Puede usar un expreso regular para eliminar todo, aparte de los caracteres que desea conservar:

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

Reemplaza todo lo que no es (^) las letras AZ o az, los números 0-9, espacio, guión bajo, guión, más y ampersand, con nada (es decir, elimínelo).

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

Esto eliminará todos los caracteres de control ( http://uk.php.net/manual/en/regexp.reference.unicode.php ) dejando los \ncaracteres de nueva línea. Desde mi experiencia, los caracteres de control son los que con mayor frecuencia causan los problemas de impresión.

Gajus
fuente
1
¡Funciona perfecto para mí! Agregué solo /upara caracteres UTF-8. ¿Podría explicar qué hace la primera parte (?!\n)?
Marcio Mazzucato
4

Para quitar todos los caracteres no ASCII de la cadena de entrada

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

Ese código elimina cualquier carácter en los rangos hexadecimales 0-31 y 128-255, dejando solo los caracteres hexadecimales 32-127 en la cadena resultante, que llamo $ resultado en este ejemplo.

Junaid Masood
fuente
3

¡La respuesta de @PaulDixon es completamente incorrecta , ya que elimina los caracteres ASCII extendidos imprimibles 128-255! ha sido parcialmente corregido. No sé por qué todavía quiere eliminar 128-255 de un conjunto ASCII de 7 bits de 127 caracteres, ya que no tiene los caracteres ASCII extendidos.

Pero finalmente fue importante no eliminar 128-255 porque, por ejemplo, chr(128)( \x80) es el símbolo del euro en ASCII de 8 bits y muchas fuentes UTF-8 en Windows muestran un símbolo del euro y Android con respecto a mi propia prueba.

Y eliminará muchos caracteres UTF-8 si elimina los caracteres ASCII 128-255 de una cadena UTF-8 (probablemente los bytes iniciales de un carácter UTF-8 de varios bytes). ¡Entonces no hagas eso! Son caracteres completamente legales en todos los sistemas de archivos utilizados actualmente. El único rango reservado es 0-31 .

En su lugar, use esto para eliminar los caracteres no imprimibles 0-31 y 127:

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

Se trabaja en ASCII y UTF-8 , porque ambas comparten el mismo rango de ajuste de control .

La alternativa más rápida y lenta¹ sin usar expresiones 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);

Si desea mantener todos los espacios en blanco \t, \ny \r, a continuación, quitar chr(9), chr(10)y chr(13)de esta lista. Nota: El espacio en blanco habitual es chr(32)para que permanezca en el resultado. Decide si quieres eliminar el espacio chr(160)que no se rompe, ya que puede causar problemas.

¹ Probado por @PaulDixon y verificado por mí mismo.

mgutt
fuente
2

qué tal si:

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

me da un control completo de lo que quiero incluir

sdfor
fuente
0

La respuesta marcada es perfecta pero pierde el carácter 127 (DEL), que también es un carácter no imprimible

mi respuesta seria

$string = preg_replace('/[\x00-\x1F\x7f-\xFF]/', '', $string);
Mubashar
fuente
Esta respuesta también es incorrecta. Ver: stackoverflow.com/a/42058165/318765
mgutt
la respuesta anterior fue un cumplido a la respuesta original que solo agrega el carácter "eliminar".
Mubashar
0

"cedivad" me resolvió el problema con el resultado persistente de los caracteres suecos ÅÄÖ.

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

¡Gracias!

Andreas Ek
fuente
0

Para cualquiera que todavía esté buscando cómo hacer esto sin eliminar los caracteres no imprimibles, sino escapar de ellos, hice esto para ayudar. ¡Siéntete libre de mejorarlo! Los caracteres se escapan a \\ x [A-F0-9] [A-F0-9].

Llame así:

$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));
    }
?>
Déjalo caer como si estuviera ardiendo
fuente
0

Resolví el problema para UTF8 usando https://github.com/neitanod/forceutf8

use ForceUTF8\Encoding;

$string = Encoding::fixUTF8($string);
mnv
fuente
1
Esta biblioteca convierte los caracteres acentuados UTF-8 y los emoticones UTF-8 en "?" símbolos Problema bastante grave por desgracia.
ChristoKiwi
0

La expresión regular en la respuesta seleccionada falla para Unicode: 0x1d (con php 7.4)

una solución:

<?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 elimina todos los caracteres invisibles excepto la nueva línea

Mkdgs
fuente