Как насчет:
/^\((?:#.+#,\s*){8}(?:#.+#\s*)\)[,;]$/gm
Это 8 неперехватывающих групп текстового поля с одним или несколькими символами, за которыми следуют запятая и необязательный пробел, и еще одно текстовое поле без запятой, заключенное в буквальные скобки, за которым следует запятая или точка с запятой. Если в одной текстовой строке несколько строк, обязательно используйте переключатель «/ m», чтобы «^» и «$» соответствовали символам новой строки.
Вы должны быть в состоянии использовать это, чтобы извлечь все действительные строки. Удаление других строк будет сложнее ...
Обновление:
Понял. Вот тот, который соответствует строкам с 8 или менее парами символов "#" или с нечетным числом из них:
^\((?:[^#\n]*?#[^#\n]*?#[,\s]?){0,8}(?:[^#]*#[^#]*)?\)[,;]\s*$
, которая будет соответствовать строке вроде:
(#text1#,#text2#,#text3#,#text4#),
или строка типа:
(#text1#,#text2#,#text3#,#text4#,#),
edit: запятая должна быть необязательной ...

Похоже, что ваши новые примеры больше не являются строковыми, и у вас больше нет "одиночного #", поэтому его можно упростить до:
\((?:[^#\n]*?#[^#\n]*?#[,\s]?){0,8}\)[,;]\s*