Here's a really complex version using conditionals:
^(?(?=#).*\n(.)|(?(?=.#).*\n.(.)|(?(?=..#).*\n..(.)|(?(?=...#).*\n...(.)|(?(?=^....#).*\n....(.)|(?(?=^.{5}#).*\n.{5}(.)|(?(?=^.{6}#).*\n.{6}(.)|#))))))).*$
Basically what it does is to first check for beginning of line and start a multi leveled conditional (if/then/else).
Then follows a test for an increasing number of any characters, starting with 0 (and in this example going up to 7) and after that it test for a match of #
.
If that is fulfilled, it matches the rest of the line, a line feed, and the same number of any character found in the previous test which puts us in the same position as the #
from the in above.
So now we just capture a single character, which is the result!
If the match with n characters followed by a #
wasn't met, it test the next else which has n+1 charaters before the #
, and so on...
If the final test fails, a match for #
is made (which in a full scale version shouldn't be present) to make the match fail.
Finally, after closing the conditional, the rest of the line is matched to avoid continued matching on the current line, if a global flag is used.
See it here at regex101.
Edit
Reduced complexity somewhat by movinf the actual capturing out of the test, to outside it at the end, before matched rest of line:
^(?(?=#).*\n|(?(?=.#).*\n.|(?(?=..#).*\n..|(?(?=...#).*\n...|(?(?=....#).*\n....|(?(?=.{5}#).*\n.{5}|(?(?=.{6}#).*\n.{6}|#)))))))(.).*$
Here at regex101.
Edit2
Kinda got stuck on this one ;)
Here's an even simpler version. (I've replaced the index character with ■
(ASCII 254), mainly to allow for comments within the regex, but also to improve readability (imo).)
^ # Match start of line
(?(?=■).*\n|. # Test if first character is a ■. If not match one of anything
(?(?=■).*\n|. # Test if second character is a ■. If not match one of anything
(?(?=■).*\n|. # Test if third character is a ■. If not match one of anything
(?(?=■).*\n|. # and so on...
(?(?=■).*\n|. # and so on...
(?(?=■).*\n|. # and so on...
(?(?=■).*\n|■ # Match a last ! or force a fail (match ■)
.).).).).).).) # "On the way out" of the match, "eat" characters on the next line
(.).*$ # Capture the character and match the rest of the line
As stated in the comments, it matches an arbitrary character after the match in all levels removing the need for specific match of each levels characters.
This one also makes it very much easier to add levels to the tests.
At regex101.