Any time you write a loop in shell just to manipulate text you have the wrong approach. In this case the script you had and the answer you selected will both be immensely inefficient and fragile and will produce bizarre unexpected output and/or syntax errors depending on the contents of the directory you run them in.
The UNIX tool for manipulating text is awk - just use it (in this case I'm using GNU awk for ENDFILE):
$ cat good.php
<html>
<head>
<title>PHP Test</title>
</head>
<body>
<?php echo '<p>Hello World</p>'; ?>
<?php echo '<p>Goodbye Cruel World</p>'; ?>
</body>
</html>
.
$ cat bad.php
<html>
<head>
<title>PHP Test</title>
</head>
<body>
<?php echo '<p>Hello World</p>';
<?php echo '<p>Goodbye Cruel World</p>'; ?>
<?php echo '<p>Hello Again World</p>'; ?>
echo '<p>Goodbye Again Cruel World</p>'; ?>
</body>
</html>
.
awk '/<\?php/{++beg} /\?>/{++end} ENDFILE{print FILENAME, beg, end; beg=end=0}' *.php
bad.php 3 3
good.php 2 2
The above uses 1 process total for all files instead of 4 per file for your shell script so it will be orders of magnitude more efficient and it will work for ANY file names, including those that contain white space or even newlines.
Note that, just like your shell loop would behave, the above does not actually detect the mismatches in the bad.php file since there's one missing open and 1 missing close. Fortunately it's also easy to enhance to, say, tell you the line numbers and contents where the open/close lines are mismatching and report them as they occur:
$ awk '
FNR==1 { beg=end=0 }
/<\?php/ {
if (beg++ > end) {
print "Warning:", FILENAME, "missing close for the open at line", begFnr, begRec
beg--
}
begFnr = FNR
begRec = $0
}
/\?>/ {
if (++end > beg) {
print "Warning:", FILENAME, "missing open for the close at line", FNR, $0
end--
}
}
ENDFILE {
if (beg > end) {
print "Warning:", FILENAME, "missing close for the open at line", begFnr, begRec
}
}
' *.php
Warning: bad.php missing close for the open at line 6 <?php echo '<p>Hello World</p>';
Warning: bad.php missing open for the close at line 9 echo '<p>Goodbye Again Cruel World</p>'; ?>
BTW I use beg
/end
as the var names above instead of open
/close
because close()
is an awk function name.