PHP: Jak usunąć wszystkie niedrukowalne znaki w ciągu?

160

Wyobrażam sobie, że muszę usunąć znaki 0-31 i 127,

Czy istnieje funkcja lub fragment kodu, który umożliwia wydajne wykonanie tego zadania.

Stewart Robinson
źródło

Odpowiedzi:

354

7-bitowy ASCII?

Jeśli twój Tardis właśnie wylądował w 1963 roku i chcesz tylko 7-bitowych znaków ASCII do druku, możesz wydrzeć wszystko od 0-31 i 127-255 za pomocą tego:

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

Dopasowuje wszystko z zakresu 0-31, 127-255 i usuwa to.

8-bitowy rozszerzony ASCII?

Wpadłeś w Hot Tub Time Machine i wróciłeś do lat osiemdziesiątych. Jeśli masz jakąś formę 8-bitowego ASCII, możesz chcieć zachować znaki w zakresie 128-255. Łatwa regulacja - wystarczy spojrzeć na 0-31 i 127

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

UTF-8?

Ach, witaj z powrotem w XXI wieku. Jeśli masz ciąg zakodowany w UTF-8, wówczas /u modyfikator może być użyty w wyrażeniu regularnym

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

To tylko usuwa 0-31 i 127. Działa to w ASCII i UTF-8, ponieważ oba mają ten sam zakres zestawu kontrolnego (jak zauważył mgutt poniżej). Ściśle mówiąc, działałoby to bez rozszerzenia/u modyfikatora. Ale to ułatwia życie, jeśli chcesz usunąć inne znaki ...

Jeśli masz do czynienia z Unicode, istnieje potencjalnie wiele elementów niedrukowalnych , ale rozważmy prosty: BEZ PRZERWY SPACE (U + 00A0)

W łańcuchu UTF-8 byłoby to zakodowane jako 0xC2A0. Możesz poszukać i usunąć tę konkretną sekwencję, ale mając /umodyfikator na miejscu, możesz po prostu dodać \xA0do klasy postaci:

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

Dodatek: A co z str_replace?

preg_replace jest dość wydajne, ale jeśli wykonujesz tę operację dużo, możesz zbudować tablicę znaków, które chcesz usunąć, i użyć str_replace, jak zauważył mgutt poniżej, np.

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

Intuicyjnie wydaje się, że byłoby to szybkie, ale nie zawsze tak jest, zdecydowanie powinieneś przetestować test porównawczy, aby sprawdzić, czy cokolwiek zaoszczędzi. Wykonałem kilka testów porównawczych dla różnych długości ciągów z losowymi danymi i ten wzorzec pojawił się przy użyciu 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

Same czasy dotyczą 10000 iteracji, ale bardziej interesujące są względne różnice. Do 512 znaków, widziałem, że preg_replace zawsze wygrywa. W zakresie 1-8 kb str_replace miał marginalną krawędź.

Pomyślałem, że to ciekawy wynik, więc dołączam go tutaj. Ważne jest, aby nie brać tego wyniku i używać go do decydowania, której metody użyć, ale porównać je z własnymi danymi, a następnie zdecydować.

Paul Dixon
źródło
14
Jeśli chcesz uznać nowy wiersz za bezpieczny, zmień wyrażenie na to (odwrotnie szukaj elementów drukowalnych): preg_replace (/ [^ \ x0A \ x20- \ x7E] /, '', $ string);
Nick
12
@Dalin Nie ma czegoś takiego jak „znak UTF-8”. Istnieją symbole / znaki Unicode, a UTF-8 to kodowanie, które może reprezentować je wszystkie. Miałeś na myśli powiedzieć, że to nie działa dla znaków spoza zestawu znaków ASCII.
Mathias Bynens
3
Jeśli chcesz dopasować znak Unicode powyżej \ xFF, użyj \ x {####}
Peter Olson,
przegapiłeś \ x7F (127), który jest znakiem niedrukowalnym
Mubashar
usunie to arabskie litery, złe rozwiązanie.
Ayman Hussein
141

Wiele z pozostałych odpowiedzi nie uwzględnia znaków Unicode (np. Öäüßйȝîûηы ე மி ᚉ ⠛). W takim przypadku możesz skorzystać z:

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

W zakresie znajduje się dziwna klasa znaków \x80-\x9F(tuż nad 7-bitowym zakresem znaków ASCII), które są technicznie znakami kontrolnymi, ale z czasem były nadużywane w przypadku znaków drukowalnych. Jeśli nie masz z nimi żadnych problemów, możesz użyć:

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

Jeśli chcesz również usunąć wysunięcia wiersza, powrót karetki, tabulatory, nierozdzielające spacje i miękkie łączniki, możesz użyć:

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

Zauważ, że ty w powyższych przykładach musisz używać pojedynczych cudzysłowów.

Jeśli chcesz usunąć wszystko oprócz podstawowych drukowalnych znaków ASCII (wszystkie powyższe przykładowe znaki zostaną usunięte), możesz użyć:

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

Więcej informacji można znaleźć pod adresem http://www.fileformat.info/info/charset/UTF-8/list.htm

Dalin
źródło
1
Twoje wyrażenie regularne dobrze obsługuje znaki UTF8; ale usuwa "specjalne" znaki inne niż UTF8; jak ç, ü i ö. '/[\x00-\x1F\x80-\xC0]/u'pozostawia je nienaruszone; ale także znak dzielenia (F7) i mnożenia (D7).
Hazar
@Hazar tak, masz rację \ x80- \ xFF za bardzo usunięty, ale \ x80- \ xC0 jest nadal zbyt restrykcyjny. To pomija inne drukowalne znaki, takie jak © £ ±. Dla odniesienia zobacz utf8-chartable.de
Dalin,
1
@TimMalone, ponieważ PHP rozszerzy te sekwencje znaków: php.net/manual/en/, więc wyrażenie regularne nie zobaczy zakresu, o którym próbujesz powiedzieć.
Dalin
1
A co z 7F? Nie powinno \x7F-\x9F?
Bell
1
Po prostu dużo próbowałem, wypróbowałem każdą funkcję kodowania dostępną w PHP od regex do mb_ do htmlspecialchars itp. Nic nie usunęło znaków sterujących, dzięki za zainwestowanie w pracę.
John
29

Począwszy od PHP 5.2 mamy również dostęp do zmiennej filter_var, o której nie widziałem żadnej wzmianki, więc pomyślałem, że ją tam wyrzucę. Aby użyć zmiennej filter_var do usunięcia niedrukowalnych znaków <32 i> 127, możesz:

Filtruj znaki ASCII poniżej 32

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

Filtruj znaki ASCII powyżej 127

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

Usuń oba:

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

Możesz także zakodować małe znaki w html (nowa linia, tabulacja itp.), Jednocześnie usuwając wysokie:

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

Istnieją również opcje usuwania kodu HTML, oczyszczania wiadomości e-mail i adresów URL itp. Tak więc wiele opcji oczyszczania (usuwanie danych), a nawet walidacji (zwraca false, jeśli nie jest poprawne, zamiast cichego usuwania).

Sanityzacja: http://php.net/manual/en/filter.filters.sanitize.php

Walidacja: http://php.net/manual/en/filter.filters.validate.php

Jednak nadal istnieje problem, że FILTER_FLAG_STRIP_LOW usunie znak nowej linii i powrotu karetki, które dla obszaru tekstowego są całkowicie prawidłowymi znakami ... więc niektóre odpowiedzi Regex są, jak sądzę, czasami potrzebne, np. Po przejrzeniu tego wątku, planuję to zrobić dla obszarów tekstowych:

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

Wydaje się to bardziej czytelne niż kilka wyrażeń regularnych, które zostały usunięte przez zakres liczbowy.

Kevin Nelson
źródło
27

możesz używać klas postaci

/[[:cntrl:]]+/
ghostdog74
źródło
czy to jednak nie wymaga ode mnie używania ereg?
Stewart Robinson
18

to jest prostsze:

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

jacktrade
źródło
5
Spowoduje to również usunięcie nowych wierszy, powrotu karetki i znaków UTF8.
Dalin
5
@Dalin Nie ma czegoś takiego jak „znak UTF-8”. Istnieją symbole / znaki Unicode, a UTF-8 to kodowanie, które może reprezentować je wszystkie. Chciałeś powiedzieć, że usuwa to również znaki spoza zakresu ASCII .
Mathias Bynens
1
Zjada arabskie znaki :)
Rolf
16

Wszystkie rozwiązania działają częściowo, a nawet poniżej prawdopodobnie nie obejmuje wszystkich przypadków. Mój problem polegał na próbie wstawienia ciągu do tabeli utf8 mysql. Łańcuch (i jego bajty) był zgodny z utf8, ale miał kilka złych sekwencji. Zakładam, że większość z nich była kontrolna lub formatująca.

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

Aby jeszcze bardziej zaostrzyć problem, tabela, serwer, połączenie, renderowanie treści, o czym trochę tutaj mówiliśmy

Wayne Weibel
źródło
1
Jedyny, który przeszedł wszystkie moje testy jednostkowe, super!
Korri
\ xE2 \ x80 [\ xA4- \ xA8] (lub 226.128. [164-168]) - jest błędna, sekwencja zawiera kolejne drukowalne symbole: znak Unicode „ONE DOT LEADER” (U + 2024), Unicode „DOT” LEADER ”(U + 2025), znak Unicode„ HORIZONTAL ELLIPSIS ”(U + 2026), znak Unicode„ PUNKT ŁĄCZENIA ”(U + 2027). I tylko jeden niedrukowalny: znak Unicode „LINE SEPARATOR” (U + 2028). Kolejny też jest niedrukowalny: Unicode Character 'PARAGRAPH SEPARATOR' (U + 2029). Więc zamień sekwencję na: \ xE2 \ x80 [\ xA8- \ xA9] \ xE2 \ x80 [\ xA8- \ xA9], aby usunąć SEPARATOR LINII i SEPARATOR PARAGRAFÓW.
MingalevME
To najlepsze rozwiązanie, jakie udało mi się do tej pory znaleźć, ale musiałem laso dodać, $s = preg_replace('/(\xF0\x9F[\x00-\xFF][\x00-\xFF])/', ' ', $s);ponieważ wszystkie postacie emoji psowały mysql
Joe Black
9

Moja wersja zgodna z UTF-8:

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

cedivad
źródło
7
To dobrze usuwa znaki, takie jak cudzysłowy, nawiasy, itp. Z pewnością są to znaki drukowalne.
Gajus
to jest cudowne! to uratowało mi życie, pomieszało przy drukowaniu znaków arabskich, działało jak mistrz :)
krishna
6

Możesz użyć wyrażenia regularnego, aby usunąć wszystko oprócz tych znaków, które chcesz zachować:

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

Zastępuje wszystko, co nie jest (^) literami AZ lub az, cyframi 0-9, spacją, podkreśleniem, łącznikiem, plusem i ampersandem - niczym (tzn. Usuń to).

Richy B.
źródło
5
preg_replace('/(?!\n)[\p{Cc}]/', '', $response);

Spowoduje to usunięcie wszystkich znaków kontrolnych ( http://uk.php.net/manual/en/regexp.reference.unicode.php ), pozostawiając \nznaki nowej linii. Z mojego doświadczenia wynika, że ​​znaki sterujące są tymi, które najczęściej powodują problemy z drukowaniem.

Gajus
źródło
1
U mnie działa idealnie! Dodałem tylko /udla znaków UTF-8. Czy mógłbyś wyjaśnić, co (?!\n)robi pierwsza część ?
Marcio Mazzucato
4

Aby usunąć wszystkie znaki spoza zestawu ASCII z ciągu wejściowego

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

Ten kod usuwa wszystkie znaki z zakresów szesnastkowych 0-31 i 128-255, pozostawiając tylko znaki szesnastkowe 32-127 w ciągu wynikowym, który w tym przykładzie nazywam $ result.

Junaid Masood
źródło
3

Odpowiedź @PaulDixon jest całkowicie błędne , ponieważ usuwa druku rozszerzonych znaków ASCII 128-255!został częściowo poprawiony. Nie wiem, dlaczego nadal chce usunąć 128-255 z zestawu 127 znaków 7-bitowego ASCII, ponieważ nie ma on rozszerzonych znaków ASCII.

Ale ostatecznie ważne było, aby nie usuwać 128-255, ponieważ na przykład chr(128)( \x80) to znak euro w 8-bitowym ASCII, a wiele czcionek UTF-8 w systemie Windows wyświetla znak euro i Androida dotyczący mojego własnego testu.

I zabije wiele znaków UTF-8, jeśli usuniesz znaki ASCII 128-255 z łańcucha UTF-8 (prawdopodobnie początkowe bajty wielobajtowego znaku UTF-8). Więc nie rób tego! Są to całkowicie legalne postacie we wszystkich obecnie używanych systemach plików. Jedyny zarezerwowany zakres to 0-31 .

Zamiast tego użyj tego, aby usunąć niedrukowalne znaki 0-31 i 127:

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

To działa w ASCII i UTF-8 , ponieważ zarówno akcję samym przedziale zestaw sterujący .

Najszybciej slower¹ alternatywa bez użycia wyrażeń regularnych:

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

Jeśli chcesz zachować wszystkie białe znaki \t, \ni \r, a następnie usunąć chr(9), chr(10)i chr(13)z tej listy. Uwaga: Zwykła biała spacja chr(32)pozostaje w wyniku. Zdecyduj sam, czy chcesz usunąć niepękającą przestrzeń, chr(160)ponieważ może to powodować problemy.

¹ Przetestowane przez @PaulDixon i zweryfikowane przeze mnie.

mgutt
źródło
2

Co powiesz na:

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

daje mi pełną kontrolę nad tym, co chcę uwzględnić

sdfor
źródło
0

Oznaczony anwser jest doskonały, ale pomija znak 127 (DEL), który jest również znakiem niedrukowalnym

moja odpowiedź brzmiałaby

$string = preg_replace('/[\x00-\x1F\x7f-\xFF]/', '', $string);
Mubaszar
źródło
Ta odpowiedź też jest błędna. Zobacz: stackoverflow.com/a/42058165/318765
mgutt
powyższa odpowiedź była komplementem do oryginalnej odpowiedzi, która tylko dodaje znak „usuń”.
Mubashar
0

"cedivad" rozwiązał dla mnie problem z trwałym wynikiem szwedzkich znaków ÅĘÖ.

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

Dzięki!

Andreas Ek
źródło
0

Dla każdego, kto wciąż szuka, jak to zrobić bez usuwania niedrukowalnych znaków, ale raczej przed nimi, zrobiłem to, aby pomóc. Zapraszam do ulepszania! Znaki są zapisywane do \\ x [A-F0-9] [A-F0-9].

Zadzwoń tak:

$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
źródło
0

Rozwiązałem problem dla UTF8 używając https://github.com/neitanod/forceutf8

use ForceUTF8\Encoding;

$string = Encoding::fixUTF8($string);
mnv
źródło
1
Ta biblioteka konwertuje znaki akcentowane UTF-8 i emotikony UTF-8 na „?” symbolika. Niestety dość poważny problem.
ChristoKiwi,
0

Wyrażenie regularne do wybranej odpowiedzi nie działa dla Unicode: 0x1d (z php 7.4)

rozwiązanie:

<?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 Łańcuch usuwa wszystkie niewidoczne znaki z wyjątkiem nowej linii

Mkdgs
źródło