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? Commented 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. Commented Nov 17, 2022 at 14:55
  • Is ack supposed to be awk? Commented 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. Commented Nov 17, 2022 at 15:00
  • 2
    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? Commented 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'

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .