preg_split()
(PHP 4, PHP 5, PHP 7)
通过一个正则表达式分隔字符串
说明
preg_split(string $pattern,string $subject[,int $limit= -1[,int $flags= 0]]): array
通过一个正则表达式分隔给定字符串.
参数
- $pattern
用于搜索的模式,字符串形式。
- $subject
输入字符串
- $limit
如果指定,将限制分隔得到的子串最多只有$limit个,返回的最后一个子串将包含所有剩余部分。$limit值为-1, 0或null时都代表"不限制",作为php的标准,你可以使用null跳过对$flags的设置。
- $flags
flags
可以是任何下面标记的组合(以位或运算|组合):PREG_SPLIT_NO_EMPTY
- 如果这个标记被设置,preg_split()将仅返回分隔后的非空部分。
PREG_SPLIT_DELIM_CAPTURE
- 如果这个标记设置了,用于分隔的模式中的括号表达式将被捕获并返回。
PREG_SPLIT_OFFSET_CAPTURE
如果这个标记被设置,对于每一个出现的匹配返回时将会附加字符串偏移量.注意:这将会改变返回数组中的每一个元素,使其每个元素成为一个由第0个元素为分隔后的子串,第1个元素为该子串在$subject中的偏移量组成的数组。
返回值
返回一个使用$pattern边界分隔$subject后得到的子串组成的数组,或者在失败时返回FALSE
。
范例
preg_split()示例:获取搜索字符串的部分
<?php //使用逗号或空格(包含" ", \r, \t, \n, \f)分隔短语 $keywords = preg_split("/[\s,]+/", "hypertext language, programming"); print_r($keywords); ?>
以上例程会输出:
Array ( [0] => hypertext [1] => language [2] => programming )
将一个字符串分隔为组成它的字符
<?php $str = 'string'; $chars = preg_split('//', $str, -1, PREG_SPLIT_NO_EMPTY); print_r($chars); ?>
以上例程会输出:
Array ( [0] => s [1] => t [2] => r [3] => i [4] => n [5] => g )
分隔一个字符串并获取每部分的偏移量
<?php $str = 'hypertext language programming'; $chars = preg_split('/ /', $str, -1, PREG_SPLIT_OFFSET_CAPTURE); print_r($chars); ?>
以上例程会输出:
Array ( [0] => Array ( [0] => hypertext [1] => 0 ) [1] => Array ( [0] => language [1] => 10 ) [2] => Array ( [0] => programming [1] => 19 ) )
注释
Tip如果你不需要正则表达式功能,可以有更快(并且更简单)的选择比如explode()或str_split()。
Tip如果没有成功匹配,将会返回一个数组,包含了单个元素,即输入的字符串。
参见
- PCRE 模式
preg_quote()
转义正则表达式字符implode()
将一个一维数组的值转化为字符串preg_match()
执行匹配正则表达式preg_match_all()
执行一个全局正则表达式匹配preg_replace()
执行一个正则表达式的搜索和替换preg_last_error()
返回最后一个PCRE正则执行产生的错误代码
Sometimes PREG_SPLIT_DELIM_CAPTURE does strange results. <?php $content = '<strong>Lorem ipsum dolor</strong> sit <img src="test.png" />amet <span class="test" style="color:red">consec<i>tet</i>uer</span>.'; $chars = preg_split('/<[^>]*[^\/]>/i', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); print_r($chars); ?> Produces: Array ( [0] => Lorem ipsum dolor [1] => sit <img src="test.png" />amet [2] => consec [3] => tet [4] => uer ) So that the delimiter patterns are missing. If you wanna get these patters remember to use parentheses. <?php $chars = preg_split('/(<[^>]*[^\/]>)/i', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); print_r($chars); //parentheses added ?> Produces: Array ( [0] => <strong> [1] => Lorem ipsum dolor [2] => </strong> [3] => sit <img src="test.png" />amet [4] => <span class="test" style="color:red"> [5] => consec [6] => <i> [7] => tet [8] => </i> [9] => uer [10] => </span> [11] => . )
Extending m.timmermans's solution, you can use the following code as a search expression parser: <?php $search_expression = "apple bear \"Tom Cruise\" or 'Mickey Mouse' another word"; $words = preg_split("/[\s,]*\\\"([^\\\"]+)\\\"[\s,]*|" . "[\s,]*'([^']+)'[\s,]*|" . "[\s,]+/", $search_expression, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); print_r($words); ?> The result will be: Array ( [0] => apple [1] => bear [2] => Tom Cruise [3] => or [4] => Mickey Mouse [5] => another [6] => word ) 1. Accepted delimiters: white spaces (space, tab, new line etc.) and commas. 2. You can use either simple (') or double (") quotes for expressions which contains more than one word.
This regular expression will split a long string of words into an array of sub-strings, of some maximum length, but only on word-boundries. I use the reg-ex with preg_match_all(); but, I'm posting this example here (on the page for preg_split()) because that's where I looked when I wanted to find a way to do this. Hope it saves someone some time. <?php // example of a long string of words $long_string = 'Your IP Address will be logged with the submitted note and made public on the PHP manual user notes mailing list. The IP address is logged as part of the notes moderation process, and won\'t be shown within the PHP manual itself.'; // "word-wrap" at, for example, 60 characters or less $max_len = 60; // this regular expression will split $long_string on any sub-string of // 1-or-more non-word characters (spaces or punctuation) if(preg_match_all("/.{1,{$max_len}}(?=\W+)/", $long_string, $lines) !== False) { // $lines now contains an array of sub-strings, each will be approx. // $max_len characters - depending on where the last word ended and // the number of 'non-word' characters found after the last word for ($i=0; $i < count($lines[0]); $i++) { echo "[$i] {$lines[0][$i]}\n"; } } ?>
If you want to split by a char, but want to ignore that char in case it is escaped, use a lookbehind assertion. In this example a string will be split by ":" but "\:" will be ignored: <?php $string='a:b:c\:d'; $array=preg_split('#(?<!\\\)\:#',$string); print_r($array); ?> Results into: Array ( [0] => a [1] => b [2] => c\:d )
Here is another way to split a CamelCase string, which is a simpler expression than the one using lookaheads and lookbehinds: preg_split('/([[:upper:]][[:lower:]]+)/', $last, null, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY) It makes the entire CamelCased word the delimiter, then returns the delimiters (PREG_SPLIT_DELIM_CAPTURE) and omits the empty values between the delimiters (PREG_SPLIT_NO_EMPTY)
You must be caution when using lookbehind to a variable match. For example: '/(?<!\\\)\r?\n)/' to match a new line when not \ is before it don't go as spected as it match \r as the lookbehind (becouse isn't a \) and is optional before \n. You must use this for example: '/((?<!\\\|\r)\n)|((?<!\\\)\r\n)/' That match a alone \n (not preceded by \r or \) or a \r\n not preceded by a \.
To clarify the "limit" parameter and the PREG_SPLIT_DELIM_CAPTURE option, <?php $preg_split('(/ /)', '1 2 3 4 5 6 7 8', 4 ,PREG_SPLIT_DELIM_CAPTURE ); ?> returns: ('1', ' ', '2', ' ' , '3', ' ', '4 5 6 7 8') So you actually get 7 array items not 4
If the task is too complicated for preg_split, preg_match_all might come in handy, since preg_split is essentially a special case. I wanted to split a string on a certain character (asterisk), but only if it wasn't escaped (by a preceding backslash). Thus, I should ensure an even number of backslashes before any asterisk meant as a splitter. Look-behind in a regular expression wouldn't work since the length of the preceding backslash sequence can't be fixed. So I turned to preg_match_all: <?php // split a string at unescaped asterisks // where backslash is the escape character $splitter = "/\\*((?:[^\\\\*]|\\\\.)*)/"; preg_match_all($splitter, "*$string", $aPieces, PREG_PATTERN_ORDER); $aPieces = $aPieces[1]; // $aPieces now contains the exploded string // and unescaping can be safely done on each piece foreach ($aPieces as $idx=>$piece) $aPieces[$idx] = preg_replace("/\\\\(.)/s", "$1", $piece); ?>
preg_split() behaves differently from perl's split() if the string ends with a delimiter. This perl snippet will print 5: my @a = split(/ /, "a b c d e "); print scalar @a; The corresponding php code prints 6: <?php print count(preg_split("/ /", "a b c d e ")); ?> This is not necessarily a bug (nowhere does the documentation say that preg_split() behaves the same as perl's split()) but it might surprise perl programmers.
Limit = 1 may be confusing. The important thing is that in case of limit equals to 1 will produce only ONE substring. Ergo the only one substring will be the first one as well as the last one. Tnat the rest of the string (after the first delimiter) will be placed to the last substring. But last is the first and only one. <?php $output = $preg_split('(/ /)', '1 2 3 4 5 6 7 8', 1); echo $output[0] //will return whole string!; $output = $preg_split('(/ /)', '1 2 3 4 5 6 7 8', 2); echo $output[0] //will return 1; echo $output[1] //will return '2 3 4 5 6 7 8'; ?>
Spacing out your CamelCase using preg_replace: <?php function spacify($camel, $glue = ' ') { return preg_replace( '/([a-z0-9])([A-Z])/', "$1$glue$2", $camel ); } echo spacify('CamelCaseWords'), "\n"; // 'Camel Case Words' echo spacify('camelCaseWords'), "\n"; // 'camel Case Words' ?>
To split a camel-cased string using preg_split() with lookaheads and lookbehinds: <?php function splitCamelCase($str) { return preg_split('/(?<=\\w)(?=[A-Z])/', $str); } ?>
"... as is standard across PHP, you can use NULL to skip to the flags parameter" is not correct. With PHP 7.1 and declare(strict_types=1), specifying NULL for "limit" will not "skip" to the flag. The result is: "TypeError: preg_split() expects parameter 3 to be integer, null given". Instead of NULL, you DO need to specify an explicit integer limit, as in 0 or -1, if no limit is wanted.
Split string into words. <?php $string = 'This - is a, very dirty "string" :-)'; // split into words $wordlist = preg_split('/\W/', $string, 0, PREG_SPLIT_NO_EMPTY); // returns only words that have minimum 2 chars $wordlist = array_filter($wordlist, function($val) { return strlen($val) >= 2; }); // print var_dump($wordlist); ?> Result: array (size=5) 0 => string 'This' (length=4) 1 => string 'is' (length=2) 3 => string 'very' (length=4) 4 => string 'dirty' (length=5) 5 => string 'string' (length=6)
Assuming you're using UTF-8, this function can be used to separate Unicode text into individual codepoints without the need for the multibyte extension. <?php preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY); ?> The words "English", "Español", and "Русский" are all seven letters long. But strlen would report string lengths 7, 8 and 14, respectively. The preg_split above would return a seven-element array in all three cases. It splits '한국어' into the array ['한', '국', '어'] instead of the 9-character array that str_split($text) would produce.
This is a function to truncate a string of text while preserving the whitespace (for instance, getting an excerpt from an article while maintaining newlines). It will not jive well with HTML, of course. <?php /** * Truncates a string of text by word count * @param string $text The text to truncate * @param int $max_words The maximum number of words * @return string The truncated text */ function limit_words ($text, $max_words) { $split = preg_split('/(\s+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); $truncated = ''; for ($i = 0; $i < min(count($split), $max_words*2); $i += 2) { $truncated .= $split[$i].$split[$i+1]; } return trim($truncated); } ?>
If you need convert function arguments without default default values and references, you can try this code: <?php $func_args = '$node, $op, $a3 = NULL, $form = array(), $a4 = NULL' $call_arg = preg_match_all('@(?<func_arg>\$[^,= ]+)@i', $func_args, $matches); $call_arg = implode(',', $matches['func_arg']); ?> Result: string = "$node,$op,$a3,$form,$a4"
If you want to use something like explode(PHP_EOL, $string) but for all combinations of \r and \n, try this one: <?php $text = "A\nB\rC\r\nD\r\rE\n\nF"; $texts = preg_split("/((\r(?!\n))|((?<!\r)\n)|(\r\n))/", $text); ?> result: array("A", "B", "C", "D", "", "E", "", "F");
<?php $s = '<p>bleh blah</p><p style="one">one two three</p>'; $htmlbits = preg_split('/(<p( style="[-:a-z0-9 ]+")?>|<\/p>)/i', $s, -1, PREG_SPLIT_DELIM_CAPTURE); print_r($htmlbits); ?> Array ( [0] => [1] => <p> [2] => bleh blah [3] => </p> [4] => [5] => <p style="one"> [6] => style="one" [7] => one two three [8] => </p> [9] => ) two interesting bits: 1. When using PREG_SPLIT_DELIM_CAPTURE, if you use more than one pair of parentheses, the result array can have members representing all pairs. See array indexes 5 and 6 to see two adjacent delimiter results in which the second is a subset match of the first. 2. If a parenthesised sub-expression is made optional by a following question mark (ex: '/abc (optional subregex)?/') some split delimiters may be captured in the result while others are not. See array indexes 1 and 2 to see an instance where the overall match succeeded and returned a delimiter while the optional sub-expression '( style="[-:a-z0-9 ]+")?' did not match, and did not return a delimiter. This means it's possible to have a result with an unpredictable number of delimiters in the result array. This second aspect is true irrespective of the number of pairs of parentheses in the regex. This means: in a regular expression with a single optional parenthesised sub-expression, the overall expression can match without generating a corresponding delimiter in the result.
Hope this helps someone... <?php /** * Split a string into groups of words with a line no longer than $max * characters. * * @param string $string * @param integer $max * @return array **/ function split_words($string, $max = 1) { $words = preg_split('/\s/', $string); $lines = array(); $line = ''; foreach ($words as $k => $word) { $length = strlen($line . ' ' . $word); if ($length <= $max) { $line .= ' ' . $word; } else if ($length > $max) { if (!empty($line)) $lines[] = trim($line); $line = $word; } else { $lines[] = trim($line) . ' ' . $word; $line = ''; } } $lines[] = ($line = trim($line)) ? $line : $word; return $lines; } ?>
When using the PREG_SPLIT_OFFSET_CAPTURE option you will end up with all results in a single array, which is often undesirable as it means you then have to filter out any delimiters you wanted to check for but not keep. To get around this you can instead use preg_match_all() to perform the split. For comparison, here are two examples, both splitting around colon and semi-colon characters: <?php $pieces_with_delimiters = preg_split('/[;:]/', $input, -1, PREG_SPLIT_OFFSET_CAPTURE); ?> <?php preg_match_all('/([^;:]*)([;:]|$)/', $input, $matches); list(, $pieces, $delimiters) = $matches ?> The latter requires a more complex pattern, but produces a much more convenient set of results to work with, depending upon what you want to do with them.