PHP: Bagaimana menghapus semua karakter yang tidak dapat dicetak dalam sebuah string?

161

Saya membayangkan saya perlu menghapus karakter 0-31 dan 127,

Apakah ada fungsi atau potongan kode untuk melakukan ini secara efisien.

Stewart Robinson
sumber

Jawaban:

355

7 bit ASCII?

Jika Tardis Anda baru saja mendarat pada tahun 1963, dan Anda hanya ingin 7-bit ASCII char yang dapat dicetak, Anda dapat merobek semuanya dari 0-31 dan 127-255 dengan ini:

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

Ini cocok dengan apa pun dalam kisaran 0-31, 127-255 dan menghapusnya.

8 bit ASCII diperpanjang?

Anda jatuh ke dalam Hot Tub Time Machine, dan Anda kembali di tahun delapan puluhan. Jika Anda memiliki beberapa bentuk ASCII 8 bit, maka Anda mungkin ingin menyimpan karakter dalam kisaran 128-255. Penyesuaian mudah - hanya mencari 0-31 dan 127

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

UTF-8?

Ah, selamat datang kembali ke abad ke-21. Jika Anda memiliki string yang dikodekan UTF-8, maka /u pengubahnya dapat digunakan pada regex

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

Ini hanya menghilangkan 0-31 dan 127. Ini bekerja di ASCII dan UTF-8 karena keduanya berbagi rentang set kontrol yang sama (seperti dicatat oleh mgutt di bawah). Sebenarnya, ini akan bekerja tanpa /upengubah. Tapi itu membuat hidup lebih mudah jika Anda ingin menghapus karakter lain ...

Jika Anda berurusan dengan Unicode, ada banyak elemen non-cetak yang berpotensi , tetapi mari pertimbangkan yang sederhana: RUANG TANPA BREAK (U + 00A0)

Dalam string UTF-8, ini akan dikodekan sebagai 0xC2A0. Anda bisa mencari dan menghapus urutan tertentu, tetapi dengan /upengubah di tempat, Anda bisa menambahkan \xA0ke kelas karakter:

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

Tambahan: Bagaimana dengan str_replace?

preg_replace cukup efisien, tetapi jika Anda sering melakukan operasi ini, Anda bisa membuat array karakter yang ingin Anda hapus, dan menggunakan str_replace seperti dicatat oleh mgutt di bawah ini, mis.

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

Secara intuitif, ini sepertinya cepat, tetapi tidak selalu demikian, Anda harus melakukan tolok ukur untuk melihat apakah itu menyelamatkan Anda. Saya melakukan beberapa tolok ukur di berbagai panjang string dengan data acak, dan pola ini muncul menggunakan 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

Pengaturan waktu sendiri untuk 10.000 iterasi, tetapi yang lebih menarik adalah perbedaan relatifnya. Hingga 512 karakter, saya melihat preg_replace alway win. Dalam rentang 1-8kb, str_replace memiliki tepi marjinal.

Saya pikir itu hasil yang menarik, jadi termasuk di sini. Yang penting adalah tidak mengambil hasil ini dan menggunakannya untuk memutuskan metode mana yang akan digunakan, tetapi untuk membandingkan data Anda sendiri dan kemudian memutuskan.

Paul Dixon
sumber
14
Jika Anda perlu mempertimbangkan keamanan baris baru, ubah ekspresi menjadi ini (cari terbalik untuk printables): preg_replace (/ [^ \ x0A \ x20- \ x7E] /, '', $ string);
Nick
12
@Dalin Tidak ada yang namanya "karakter UTF-8". Ada simbol / karakter Unicode, dan UTF-8 adalah penyandian yang dapat mewakili semuanya. Anda bermaksud mengatakan ini tidak berfungsi untuk karakter di luar rangkaian karakter ASCII.
Mathias Bynens
3
Jika Anda perlu mencocokkan karakter unicode di atas \ xFF, gunakan \ x {####}
Peter Olson
Anda melewatkan \ x7F (127) yang merupakan karakter yang tidak dapat dicetak
Mubashar
ini akan menghapus huruf Arab, solusi buruk.
Ayman Hussein
141

Banyak jawaban lain di sini tidak memperhitungkan karakter unicode (mis. Öäüßйȝîûηы ე மி ᚉ ⠛). Dalam hal ini Anda dapat menggunakan yang berikut:

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

Ada kelas karakter yang aneh dalam rentang \x80-\x9F(Tepat di atas rentang ASCII 7-bit karakter) yang secara teknis mengontrol karakter, tetapi seiring waktu telah disalahgunakan untuk karakter yang dapat dicetak. Jika Anda tidak memiliki masalah dengan ini, maka Anda dapat menggunakan:

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

Jika Anda juga ingin menghapus feed garis, carriage return, tab, spasi tanpa putus, dan tanda hubung lunak, Anda dapat menggunakan:

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

Perhatikan bahwa Anda harus menggunakan tanda kutip tunggal untuk contoh di atas.

Jika Anda ingin menghapus semuanya kecuali karakter ASCII dasar yang dapat dicetak (semua karakter contoh di atas akan dilucuti) Anda dapat menggunakan:

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

Untuk referensi lihat http://www.fileformat.info/info/charset/UTF-8/list.htm

Dalin
sumber
1
Regexp Anda menangani karakter UTF8 dengan baik; tetapi menghapus karakter "khusus" non-UTF8; seperti ç, ü dan ö. '/[\x00-\x1F\x80-\xC0]/u'biarkan mereka utuh; tetapi juga tanda pembagian (F7) dan multiplikasi (D7).
Hazar
@Hazar ya Anda benar \ x80- \ xFF dilucuti terlalu banyak, tetapi \ x80- \ xC0 masih terlalu ketat. Ini akan kehilangan karakter yang dapat dicetak lainnya seperti © £ ±. Untuk referensi, lihat utf8-chartable.de
Dalin
1
@TimMalone karena PHP akan memperluas sekuens karakter tersebut: php.net/manual/en/… sehingga regex tidak akan melihat rentang yang ingin Anda ceritakan.
Dalin
1
Bagaimana dengan 7F? Bukankah seharusnya demikian \x7F-\x9F?
Bel
1
Saya baru saja mencoba banyak, saya mencoba setiap fungsi penyandian yang tersedia dalam PHP dari regex ke mb_ ke htmlspecialchars dll. Tidak ada yang menghapus karakter kontrol, terima kasih telah menginvestasikan pekerjaan.
John
29

Dimulai dengan PHP 5.2, kami juga memiliki akses ke filter_var, yang saya belum melihat menyebutkan jadi saya akan membuangnya di sana. Untuk menggunakan filter_var untuk menghapus karakter yang tidak dapat dicetak <32 dan> 127, Anda dapat melakukan:

Saring karakter ASCII di bawah 32

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

Saring karakter ASCII di atas 127

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

Lepaskan keduanya:

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

Anda juga dapat menyandikan-html karakter rendah (baris baru, tab, dll.) Sambil menelanjangi tinggi:

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

Ada juga opsi untuk menghapus HTML, membersihkan email dan URL, dll. Jadi, banyak pilihan untuk sanitasi (menghapus data) dan bahkan validasi (mengembalikan false jika tidak valid daripada stripping diam-diam).

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

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

Namun, masih ada masalah, bahwa FILTER_FLAG_STRIP_LOW akan menghapus baris baru dan pengembalian carriage, yang untuk textarea adalah karakter yang benar-benar valid ... jadi beberapa jawaban Regex, saya kira, masih diperlukan pada waktu-waktu tertentu, misalnya setelah meninjau ini utas, saya berencana untuk melakukan ini untuk textareas:

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

Ini tampaknya lebih mudah dibaca daripada sejumlah regex yang dilucuti oleh rentang numerik.

Kevin Nelson
sumber
27

Anda dapat menggunakan kelas karakter

/[[:cntrl:]]+/
ghostdog74
sumber
bukankah ini mengharuskan saya untuk menggunakan ereg?
Stewart Robinson
18

ini lebih sederhana:

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

jacktrade
sumber
5
Ini juga menghapus garis feed, carriage return, dan karakter UTF8.
Dalin
5
@Dalin Tidak ada yang namanya "karakter UTF-8". Ada simbol / karakter Unicode, dan UTF-8 adalah penyandian yang dapat mewakili semuanya. Anda bermaksud mengatakan karakter strip ini di luar rentang ASCII juga.
Mathias Bynens
1
Makan karakter Arab :)
Rolf
16

Semua solusi bekerja sebagian, dan bahkan di bawah ini mungkin tidak mencakup semua kasus. Masalah saya adalah mencoba memasukkan string ke tabel mysql utf8. String (dan bytes-nya) semuanya sesuai dengan utf8, tetapi memiliki beberapa urutan yang buruk. Saya berasumsi bahwa kebanyakan dari mereka adalah kontrol atau pemformatan.

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

Untuk lebih memperburuk masalah adalah tabel vs server vs koneksi vs rendering konten, seperti yang dibicarakan di sini

Wayne Weibel
sumber
1
Satu-satunya yang lulus semua tes unit saya, luar biasa!
Korri
\ xE2 \ x80 [\ xA4- \ xA8] (atau 226.128. [164-168]) - salah, urutannya termasuk simbol yang dapat dicetak berikutnya: Karakter Unicode 'ONE DOT LEADER' (U + 2024), Karakter Unicode 'DUA DOT PEMIMPIN '(U + 2025), Karakter Unicode' HORIZONTAL ELLIPSIS '(U + 2026), Karakter Unicode' POINT HYPHENATION '(U + 2027). Dan hanya satu yang tidak dapat dicetak: Karakter Unicode 'LINE SEPARATOR' (U + 2028). Berikutnya juga tidak dapat dicetak: Karakter Unicode 'PARAGRAPH SEPARATOR' (U + 2029). Jadi, ganti urutan dengan: \ xE2 \ x80 [\ xA8- \ xA9] \ xE2 \ x80 [\ xA8- \ xA9] untuk menghapus LINE SEPARATOR dan PARAGRAPH SEPARATOR.
MingalevME
Ini adalah solusi terbaik yang bisa saya temukan sejauh ini, tetapi saya harus menambahkan $s = preg_replace('/(\xF0\x9F[\x00-\xFF][\x00-\xFF])/', ' ', $s);karena semua karakter emoji mengacaukan mysql
Joe Black
9

Versi yang sesuai dengan UTF-8 saya:

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

cedivad
sumber
7
Ini juga menghapus karakter seperti tanda kutip, tanda kurung, dll. Itu pasti karakter yang dapat dicetak.
Gajus
ini luar biasa! itu menyelamatkan hidupku, kacau saat mencetak karakter Arab, bekerja seperti jagoan :)
krishna
6

Anda dapat menggunakan ekspres reguler untuk menghapus semuanya selain dari karakter yang ingin Anda pertahankan:

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

Mengganti segala sesuatu yang bukan (^) huruf AZ atau az, angka 0-9, spasi, garis bawah, tanda hubung, plus dan ampersand - dengan tidak ada (yaitu menghapusnya).

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

Ini akan menghapus semua karakter kontrol ( http://uk.php.net/manual/en/regexp.reference.unicode.php ) meninggalkan \nkarakter baris baru. Dari pengalaman saya, karakter kontrol adalah yang paling sering menyebabkan masalah pencetakan.

Gajus
sumber
1
Ini berfungsi sempurna untuk saya! Saya menambahkan hanya /uuntuk karakter UTF-8. Bisakah Anda jelaskan apa yang (?!\n)dilakukan bagian pertama ?
Marcio Mazzucato
4

Untuk menghapus semua karakter non-ASCII dari string input

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

Kode itu menghilangkan karakter apa pun dalam rentang hex 0-31 dan 128-255, hanya menyisakan karakter hex 32-127 dalam string yang dihasilkan, yang saya sebut $ hasil dalam contoh ini.

Junaid Masood
sumber
3

The jawaban @PaulDixon adalah benar-benar salah , karena menghilangkan dicetak karakter ASCII diperpanjang 128-255!telah diperbaiki sebagian. Saya tidak tahu mengapa dia masih ingin menghapus 128-255 dari 127 karakter ASCII 7-bit yang ditetapkan karena tidak memiliki karakter ASCII yang diperluas.

Tetapi akhirnya penting untuk tidak menghapus 128-255 karena misalnya chr(128)( \x80) adalah tanda euro dalam ASCII 8-bit dan banyak font UTF-8 di Windows menampilkan tanda euro dan Android mengenai pengujian saya sendiri.

Dan itu akan membunuh banyak karakter UTF-8 jika Anda menghapus karakter ASCII 128-255 dari string UTF-8 (mungkin byte awal dari karakter UTF-8 multi-byte). Jadi jangan lakukan itu! Mereka adalah karakter yang sepenuhnya legal di semua sistem file yang saat ini digunakan. Kisaran yang hanya dipesan adalah 0-31 .

Alih-alih gunakan ini untuk menghapus karakter yang tidak dapat dicetak 0-31 dan 127:

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

Ia bekerja di ASCII dan UTF-8 karena keduanya berbagi rentang set kontrol yang sama .

The tercepat alternatif slower¹ tanpa menggunakan ekspresi reguler:

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

Jika Anda ingin menyimpan semua karakter spasi putih \t, \ndan \r, lalu hapus chr(9), chr(10)dan chr(13)dari daftar ini. Catatan: Ruang kosong yang biasa chr(32)jadi tetap di hasilnya. Putuskan sendiri apakah Anda ingin menghapus ruang yang tidak putus chr(160)karena dapat menyebabkan masalah.

¹ Diuji oleh @PaulDixon dan diverifikasi sendiri.

mgutt
sumber
2

bagaimana tentang:

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

memberi saya kendali penuh atas apa yang ingin saya sertakan

sdfor
sumber
0

Browser ditandai sempurna tetapi melewatkan karakter 127 (DEL) yang juga merupakan karakter yang tidak dapat dicetak

jawaban saya adalah

$string = preg_replace('/[\x00-\x1F\x7f-\xFF]/', '', $string);
Mubashar
sumber
Jawaban ini juga salah. Lihat: stackoverflow.com/a/42058165/318765
mgutt
jawaban di atas adalah pujian untuk jawaban asli yang hanya menambahkan karakter "hapus".
Mubashar
0

"cedivad" memecahkan masalah bagi saya dengan hasil terus-menerus dari karakter Swedia ÅÄÖ.

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

Terima kasih!

Andreas Ek
sumber
0

Bagi siapa pun yang masih mencari cara untuk melakukan ini tanpa menghapus karakter yang tidak dapat dicetak, tetapi melarikan diri, saya membuat ini untuk membantu. Jangan ragu untuk memperbaikinya! Karakter diloloskan ke \\ x [A-F0-9] [A-F0-9].

Sebut seperti ini:

$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
sumber
0

Saya memecahkan masalah untuk UTF8 menggunakan https://github.com/neitanod/forceutf8

use ForceUTF8\Encoding;

$string = Encoding::fixUTF8($string);
mnv
sumber
1
Lib ini mengkonversi karakter beraksen UTF-8 dan emotikon UTF-8 menjadi "?" simbol. Sayangnya masalah yang cukup serius.
ChristoKiwi
0

Regex ke jawaban yang dipilih gagal untuk Unicode: 0x1d (dengan php 7.4)

sebuah solusi:

<?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 menghapus semua karakter yang tidak terlihat kecuali baris baru

Mkdgs
sumber