Duas pessoas podem escrever o mesmo padrão e obter resultados diferentes, porque um regex é só metade da história: suas flags decidem como ele se comporta. Um punhado delas cobre quase tudo.
As flags que importam
i(insensível a maiúsculas):abctambém casaABCeAbc. Simples, e normalmente o que você quer para casar palavras em texto de caixa mista.m(multilinha): faz^e$casarem no início e no fim de cada linha, em vez de apenas na string inteira. Essencial para varrer texto orientado a linhas.s(dotall / single-line): faz o ponto.casar também quebras de linha. Normalmente.casa qualquer caractere exceto uma quebra de linha, então um padrão que deveria abranger várias linhas falha silenciosamente até você ligar isto. Esta é a flag que as pessoas mais esquecem.x(extended / verbose): ignora espaço em branco não escapado no padrão e permite comentários, para que você possa dispor um regex complexo em várias linhas com anotações. Torna padrões longos legíveis sem mudar o que eles casam.g(global): encontra todos os casamentos em vez de parar no primeiro. Em muitas linguagens isso vive na operação (substituir-tudo, encontrar-tudo) e não no padrão, mas a ideia é a mesma.
O par que confunde
m e s soam parecidos e fazem coisas diferentes. m é sobre âncoras: onde ^ e $ caem. s é sobre o ponto: se . cruza quebras de linha. Você pode querer uma, ambas ou nenhuma. Um bug frequente é recorrer a multilinha quando na verdade você precisava de dotall: você queria que . abrangesse linhas, mas ligou a flag que só moveu as âncoras, e o casamento ainda para na primeira quebra de linha. Quando um casamento de várias linhas não está funcionando, pergunte qual dos dois comportamentos você realmente precisa antes de adicionar uma flag.