sscanf
(PHP 4 >= 4.0.1, PHP 5, PHP 7, PHP 8)
sscanf — フォーマット文字列に基づき入力を処理する
説明
関数 sscanf() は、printf()
の入力版です。sscanf() は、文字列
string
を読み込み、これを指定したフォーマット
format
に基づき解釈します。
フォーマット文字列の中のあらゆる空白文字は、入力文字列の中の
空白文字列にマッチします。つまり、フォーマット文字列の中にタブ文字
(\t
) が含まれていても、
それは入力中の半角スペースにマッチしてしまうということです。
パラメータ
string
-
入力文字列。
format
-
string
を解釈するフォーマット。 sprintf() のドキュメントにある説明と比べて、以下の違いがあります。- ロケールに対応していません。
-
F
、g
、G
およびb
はサポートしていません。 -
D
は十進数値を表します。 -
i
は基数検出つきの整数値を表します。 -
n
は処理する文字数を表します。 -
s
は、空白文字を読み取ると停止することを示します。 -
argnum$
の代わりに*
を指定すると、 この変換仕様に従った値の代入を抑制します。
vars
-
オプションで指定する参照渡しの変数に、 パースされた値が格納されます。
戻り値
この関数のパラメータが二つだけの場合、処理された値は配列として返されます。 それ以外の場合は、もしオプションのパラメータが渡されればこの関数は 割り当てられた値の数を返します。オプションのパラメータは 参照渡しにする必要があります。
format
で期待する部分文字列のほうが
実際に string
に存在するものより多い場合は
null
を返します。
例
例1 sscanf() の例
<?php
// シリアル番号を得る
list($serial) = sscanf("SN/2350001", "SN/%d");
// 続いて製造日を得る
$mandate = "January 01 2000";
list($month, $day, $year) = sscanf($mandate, "%s %d %d");
echo "Item $serial was manufactured on: $year-" . substr($month, 0, 3) . "-$day\n";
?>
オプションのパラメータが指定された場合、この関数は、代入された値の数を返します。
例2 sscanf() - オプションパラメータの使用法
<?php
// author 情報を取得し、DocBook エントリを生成
$auth = "24\tLewis Carroll";
$n = sscanf($auth, "%d\t%s %s", $id, $first, $last);
echo "<author id='$id'>
<firstname>$first</firstname>
<surname>$last</surname>
</author>\n";
?>
参考
- printf() - フォーマット済みの文字列を出力する
- sprintf() - フォーマットされた文字列を返す
- fprintf() - フォーマットされた文字列をストリームに書き込む
- vprintf() - フォーマットされた文字列を出力する
- vsprintf() - フォーマットされた文字列を返す
- vfprintf() - フォーマットされた文字列をストリームに書き込む
- fscanf() - フォーマットに基づきファイルからの入力を処理する
- number_format() - 数字を千の位毎にグループ化してフォーマットする
- date() - Unixタイムスタンプを書式化する
User Contributed Notes 16 notes
this function is a great way to get integer rgb values from the html equivalent hex.
list($r, $g, $b) = sscanf('00ccff', '%2x%2x%2x');
After playing around with this for a while, I found that if you use %[^[]] instead of %s (since php has problems with spaces when using %s) it works nicely.
For those that aren't familiar with regular expressions, %[^[]] basically matches anything that isn't nothing.
Hope this helps. - Gabe
FYI - if you are trying to scan from a string which contains a filename with extension. For instance:
<?php
$out = sscanf('file_name.gif', 'file_%s.%s', $fpart1, $fpart2);
?>
The scanned string in the $fpart1 parameter turns out to be 'name.gif' and $fpart2 will be NULL.
To get around this you can simply replace the "." with a space or another "white-space like" string sequence.
I didn't see any other comments on regarding string literals which contain a '.' so I thought I'd mention it. The subtle characteristics of having "white-space delimited" content I think can be a source of usage contention. Obviously, another way to go is regular expressions in this instance, but for newer users this may be helpful.
Just in case someone else spent 10 minutes of frustration like I did. This was seen on PHP Version 5.2.3-1ubuntu6.3.
Searching the bug reports shows another users misunderstanding: http://bugs.php.net/bug.php?id=7793
The %[^[]]-trick may seem to work, but it doesn't!
What happens is that sscanf will simply match any characters but an opening square bracket (which is rather rare and that's why it might just seem to work).
But even worse it will expect a ]-character next and continue to match anything.
Now what you can do is make sscanf look for any character but a character that is really never used... a good choice is the linebreak "%[^\\n]", especially in combination with fscanf.
What you can also do is copy and paste any unused ascii character like #001 or something.
@mikewillitsgmail.com:
<?php
$out = sscanf('file_name.gif', 'file_%[^.].%s', $fpart1, $fpart2);
echo '<pre>';
print_r($fpart1);
echo '<hr />';
print_r($fpart2);
echo '</pre>';
?>
output:
name
-
gif
The "^." part avoid the first searched string to be too greedy. But doesn't protect you against an "file_test.name.gif" input, with bad results!
Security Note:
Although it is a very powerful technique, keep in mind that it is easily deceived.
Many successful exploits have been based on scanf attacks. It should not be used on untrusted input without a lot of additional validation.
If you just wants filter out information between two parts of a string, i used the following, it works better for me then the sscanf function.
<?php
function scanstr($zoekstr,$part1,$part2) {
$firstpos=strpos ($zoekstr, $part1)+strlen($part1);
$lastpos=strpos ($zoekstr, $part2);
$scanresult=substr ($zoekstr, $firstpos, $lastpos-$firstpos);
return($scanresult);
}
echo scanstr ("var1=hello&var2=test&var3=more","var2=","&var3");
?>
I've seen several examples of people using brackets to define what look like regexp character classes. In my limited testing I don't think they are genuine character classes but they seem to be similar.
My task was to use sscanf() to parse an array of strings with the format:
number SPACE string_which_may_also_have_spaces
The normal %s conversion command treats spaces as some kind of delimiter. So, you can get the strings if you know beforehand how many "words" there will be. But, my input was variable.
Here's what I came up with: (note use of the dollar-sign 'end of string' hidden delimiter)
sscanf($string_to_parse,'%d %[^$]s',$num,$text);
This conversion command says "look for an integer, then a space, then any string up to the end of the string"
To parse a line from an Apache access log in common format:
<?php
$log = array();
$n = sscanf(trim($line), '%s %s %s [%[^]]] "%s %s %[^"]" %d %s "%[^"]" "%[^"]"',
$log['ip'],
$log['client'],
$log['user'],
$log['time'],
$log['method'],
$log['uri'],
$log['prot'],
$log['code'],
$log['bytes'],
$log['ref'],
$log['agent']
);
?>
One thing to note: unlike C/C++, a variable %n is assigned to will be counted in the return value.
apparently, sscanf always splits at spaces, even if spaces are not specified in the format. consider this script:
<?php
$str = "This is a\tsentence with\ttabs";
$scanned = sscanf($str, "%s\t%s\t%s");
echo join(" : ", $scanned);
?>
this echoes "This : is : a", not the expected "This is a : sentence with : tabs."
this behaviour is fine if your strings don't contain spaces, but if they do you'd be better off using explode().
It should also be noted that when used with sscanf both x and X produce the same output (i.e. they are case-insensitive).
<?php
var_dump(sscanf("0xdead|0XDEAD", "%X|%x")); // works
added country code (1) to phone number function:
function formatPhone($phone) {
if (empty($phone)) return "";
if (strlen($phone) == 7)
sscanf($phone, "%3s%4s", $prefix, $exchange);
else if (strlen($phone) == 10)
sscanf($phone, "%3s%3s%4s", $area, $prefix, $exchange);
else if (strlen($phone) > 10)
if(substr($phone,0,1)=='1') {
sscanf($phone, "%1s%3s%3s%4s", $country, $area, $prefix, $exchange);
}
else{
sscanf($phone, "%3s%3s%4s%s", $area, $prefix, $exchange, $extension);
}
else
return "unknown phone format: $phone";
$out = "";
$out .= isset($country) ? $country.' ' : '';
$out .= isset($area) ? '(' . $area . ') ' : '';
$out .= $prefix . '-' . $exchange;
$out .= isset($extension) ? ' x' . $extension : '';
return $out;
}
Actually sscanf()_always_ returns an array if you specify less return variables than format specifiers. i may change this to return a scalar if only a single format specifier exists.
Note that sscanf() is (almost) the complete functional equivalent of its "C" counterpart, so you can do the following to get the expected effect :
sscanf("SN/2350001","SN/%d",&$serial)
The array return was a nicety for PHP.
In PHP >= 4.3.0, if you use additional reference parameters, you will get this warning:
PHP Warning: Call-time pass-by-reference has been deprecated - argument passed by value
This clearly has the potential to cause unexpected consequences (vars left empty), and will break existing code. So don't do it! These docs need updating to say this too.
The syntax:
list($a, $b) = sscanf("hello world", "%s %s");
will work as expected, and doesn't seem to cause any problems with Apache that I've noticed.
More fun with phones! This assumes that the phone number is 10 digits, with only numeric data, but it would be easy to check the length of the string first.
function formatPhone($phone) {
if (empty($phone)) return "";
sscanf($phone, "%3d%3d%4d", $area, $prefix, $exchange);
$out = @$area ? "($area) " : "";
$out .= $prefix . '-' . $exchange;
return $out;
}