If your requirements are simple - don't include quoted or escaped angle brackets, nor nested angle bracket pairs, the problem of finding a STARTING unmatched bracket is the first position of a string starting with an open bracket, containing no internal brackets, and ending with either another open bracket or the end of the string.
In regex speak, that would be:
/(<)[^<>]*(?:$|<)/
Because you want to capture all of them, and will be using preg_match_all, you need to add in look ahead to catch the overlapping matches:
/(?=(<)[^<>]*(?:$|<))/
Similarly, the unmatched right bracket problem simplifies to the last character of a string starting with either the beginning of a string or a close bracket, and ending with the close bracket, with no bracket in between. Adding in look ahead, you get:
/(?=(?:^|>)[^<>]*(>))/
I added a couple of extra brackets to your test strings to make sure we catch the end and overlapping cases, and a replacement example:
<?php
// Left angle brackets
$x = "<span>some >text< again<< some<some tag><</some tag>vfs>vf</span><<";
$y = preg_match_all('/(?=(<)[^<>]*(?:$|<))/', $x, $match, PREG_OFFSET_CAPTURE);
echo "Test: '{$x}'
";
echo "Repl: '" . locate_replace($x, $match[1], '<') . "'
";
echo "There are {$y} extra left angle brackets at character positions:
";
echo " " . implode(", ", array_column($match[1], 1)) . "
";
// Right angle brackets
$x = "abc><span>some >text< again some<some tag></some tag>vfs>>vf</span>";
$y = preg_match_all('/(?=(?:^|>)[^<>]*(>))/', $x, $match, PREG_OFFSET_CAPTURE);
echo "Test: '{$x}'
";
echo "Repl: '" . locate_replace($x, $match[1], '>') . "'
";
echo "There are {$y} extra right angle brackets at character positions:
";
echo " " . implode(", ", array_column($match[1], 1)) . "
";
function locate_replace($x, $match_oc, $repl) {
while ($mt = array_pop($match_oc)) {
$sloc = $mt[1];
$eloc = $sloc + strlen($mt[0]);
$x = substr($x, 0, $sloc) . $repl . substr($x, $eloc);
}
return $x;
}
?>
And this produces:
Test: '<span>some >text< again<< some<some tag><</some tag>vfs>vf</span><<'
Repl: '<span>some >text< again<< some<some tag><</some tag>vfs>vf</span><<'
There are 6 extra left angle brackets at character positions:
16, 23, 24, 40, 65, 66
Test: 'abc><span>some >text< again some<some tag></some tag>vfs>>vf</span>'
Repl: 'abc><span>some >text< again some<some tag></some tag>vfs>>vf</span>'
There are 4 extra right angle brackets at character positions:
3, 15, 56, 57