I'm wondering what would be the best way (likely using grep or ack) to return lines containing unbalanced special character sets in a string? For example, if the string were:


and the specified character were a double-quote ("), this line would not be returned. However, what I'm looking for is more complicated, requiring a left and right balance, for instance a left square parenthesis ([) and a right square parenthesis (]). Then the line would be returned if the number of ([) does not equal the number of (]) such as:

  • "but this one would", this one would what? Fail or succeed? Is it considered balanced or unbalanced and why? Nov 16, 2022 at 22:01
  • @JamesRisner : Sorry, I misspoke. Correction made. That line is considered "unbalanced" because it has two left square brackets and only one right square bracket. Nov 17, 2022 at 14:55
  • Is ack supposed to be awk? Nov 17, 2022 at 14:57
  • How many permutations of unbalanced do you have? Can you put a limit on how many? (E.g. support 0 to 5 sets of and 0 to 5 sets of []) If so, you can do it with a long and complicated regex. Otherwise you will need to write a custom program to count each and verify. Nov 17, 2022 at 15:00
  • 1
    Are ][ or [] ] [ [] balanced? i.e do you need a ] to be preceded by its corresponding [ or just the number of [s to be the same as the number of ]s? Nov 17, 2022 at 15:09

1 Answer 1


With grep implementations that support a -P for perl-like regexps, you could use their recursive regexp abilities to find lines with matching [/] pairs and use -v to reverse that:

grep -Pxv '((\[(?1)\]|[^][])*+)'

Requires the [ to be followed by the corresponding ] i.e [foo] is considered balanced but not ]foo[

Or if the order doesn't matter:

grep -Pxv '((\[(?1)\]|\](?1)\[|[^][])*+)'

[foo] and ]foo[ balanced, [foo still not.

That's in effect returning the lines that don't have the same number of [s as of ]s. A simpler way to achieve that would be to just count them.

perl -lne '$l = () = /\[/g; $r = () = /\]/g; print if $l != $r'
awk 'gsub(/\[/, "&") != gsub(/\]/, "&")'

With sed, you can eliminate pairs in a loop, and print if there are remaining [s or ]s:

sed -e 'h;:1' -e 's/\[\(.*\)\]/\1/g;s/\]\(.*\)\[/\1/g;t1' -e '/[][]/!d;g'

Or if the order matters:

sed -e 'h;:1' -e 's/\[\([^][]*\)\]/\1/g;t1' -e '/[][]/!d;g'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.