Por que o findstr não manipula o caso adequadamente (em algumas circunstâncias)?

Enquanto escrevia alguns scripts recentes em cmd.exe, tive a necessidade de usar o findstr com expressões regulares – comandos cmd.exe padrão exigidos pelo cliente (sem GnuWin32 nem Cygwin nem VBS nem Powershell).

Eu só queria saber se uma variável continha quaisquer letras maiúsculas e tentava usar:

 > set myvar=abc > echo %myvar%|findstr /r "[AZ]" abc > echo %errorlevel% 0 

Quando %myvar% é definido como abc , isso realmente gera a string e configura errorlevel como 0, dizendo que uma correspondência foi encontrada.

No entanto, a variante de lista completa:

 > echo %myvar%|findstr /r "[ABCDEFGHIJKLMNOPQRSTUVWXYZ]" > echo %errorlevel% 1 

não produz a linha e configura corretamente errorlevel para 1.

Além do que, além do mais:

 > echo %myvar%|findstr /r "^[AZ]*$" > echo %errorlevel% 1 

também funciona como esperado.

Eu obviamente estou sentindo falta de algo aqui, mesmo que seja apenas o fato de que o findstr está de alguma forma quebrado.

Por que o primeiro (intervalo) regex não funciona neste caso?


E ainda mais estranheza:

 > echo %myvar%|findstr /r "[AZ]" abc > echo %myvar%|findstr /r "[AZ][AZ]" abc > echo %myvar%|findstr /r "[AZ][AZ][AZ]" > echo %myvar%|findstr /r "[A]" 

Os dois últimos acima também não saem da string !!

Eu acredito que isso é principalmente uma falha de design horrível.

Todos nós esperamos que os intervalos sejam agrupados com base no valor do código ASCII. Mas eles não – em vez disso, os intervalos são baseados em uma sequência de agrupamento que quase corresponde à seqüência padrão usada pelo SORT. EDIT – A seqüência exata de agrupamento usada pelo FINDSTR está agora disponível em https://stackoverflow.com/a/20159191/1012053, na seção intitulada Intervalos de classs de caracteres Regex [xy] .

Eu preparei um arquivo de texto contendo uma linha para cada caractere ASCII estendido de 1 a 255, excluindo 10 (LF), 13 (CR) e 26 (EOF no Windows). Em cada linha eu tenho o caractere, seguido por um espaço, seguido pelo código decimal para o caractere. Em seguida, executei o arquivo por meio de SORT e capturei a saída em um arquivo sortedChars.txt.

Agora posso testar facilmente qualquer intervalo de regex com relação a esse arquivo classificado e demonstrar como o intervalo é determinado por uma seqüência de intercalação que é quase igual à SORT.

 >findstr /nrc:"^[0-9]" sortedChars.txt 137:0 048 138:½ 171 139:¼ 172 140:1 049 141:2 050 142:² 253 143:3 051 144:4 052 145:5 053 146:6 054 147:7 055 148:8 056 149:9 057 

Os resultados não são exatamente o que esperávamos, pois os personagens 171, 172 e 253 são lançados no mix. Mas os resultados fazem todo o sentido. O prefixo do número de linha corresponde à seqüência de agrupamento SORT, e você pode ver que o intervalo corresponde exatamente de acordo com a seqüência SORT.

Aqui está outro teste de alcance que segue exatamente a sequência SORT:

 >findstr /nrc:"^[!-=]" sortedChars.txt 34:! 033 35:" 034 36:# 035 37:$ 036 38:% 037 39:& 038 40:( 040 41:) 041 42:* 042 43:, 044 44:. 046 45:/ 047 46:: 058 47:; 059 48:? 063 49:@ 064 50:[ 091 51:\ 092 52:] 093 53:^ 094 54:_ 095 55:` 096 56:{ 123 57:| 124 58:} 125 59:~ 126 60:¡ 173 61:¿ 168 62:¢ 155 63:£ 156 64:¥ 157 65:₧ 158 66:+ 043 67:∙ 249 68:< 060 69:= 061 

Há uma pequena anomalia com caracteres alfa. O caractere "a" classifica entre "A" e "Z", mas não corresponde a [AZ]. "z" classifica depois de "Z", mas corresponde a [AZ]. Existe um problema correspondente com [az]. "A" classifica antes de "a", mas corresponde a [az]. "Z" classifica entre "a" e "z", mas não corresponde a [az].

Aqui estão os resultados [AZ]:

 >findstr /nrc:"^[AZ]" sortedChars.txt 151:A 065 153:â 131 154:ä 132 155:à 133 156:å 134 157:Ä 142 158:Å 143 159:á 160 160:ª 166 161:æ 145 162:Æ 146 163:B 066 164:b 098 165:C 067 166:c 099 167:Ç 128 168:ç 135 169:D 068 170:d 100 171:E 069 172:e 101 173:é 130 174:ê 136 175:ë 137 176:è 138 177:É 144 178:F 070 179:f 102 180:ƒ 159 181:G 071 182:g 103 183:H 072 184:h 104 185:I 073 186:i 105 187:ï 139 188:î 140 189:ì 141 190:í 161 191:J 074 192:j 106 193:K 075 194:k 107 195:L 076 196:l 108 197:M 077 198:m 109 199:N 078 200:n 110 201:ñ 164 202:Ñ 165 203:ⁿ 252 204:O 079 205:o 111 206:ô 147 207:ö 148 208:ò 149 209:Ö 153 210:ó 162 211:º 167 212:P 080 213:p 112 214:Q 081 215:q 113 216:R 082 217:r 114 218:S 083 219:s 115 220:ß 225 221:T 084 222:t 116 223:U 085 224:u 117 225:û 150 226:ù 151 227:ú 163 228:ü 129 229:Ü 154 230:V 086 231:v 118 232:W 087 233:w 119 234:X 088 235:x 120 236:Y 089 237:y 121 238:ÿ 152 239:Z 090 240:z 122 

E os resultados [az]

 >findstr /nrc:"^[az]" sortedChars.txt 151:A 065 152:a 097 153:â 131 154:ä 132 155:à 133 156:å 134 157:Ä 142 158:Å 143 159:á 160 160:ª 166 161:æ 145 162:Æ 146 163:B 066 164:b 098 165:C 067 166:c 099 167:Ç 128 168:ç 135 169:D 068 170:d 100 171:E 069 172:e 101 173:é 130 174:ê 136 175:ë 137 176:è 138 177:É 144 178:F 070 179:f 102 180:ƒ 159 181:G 071 182:g 103 183:H 072 184:h 104 185:I 073 186:i 105 187:ï 139 188:î 140 189:ì 141 190:í 161 191:J 074 192:j 106 193:K 075 194:k 107 195:L 076 196:l 108 197:M 077 198:m 109 199:N 078 200:n 110 201:ñ 164 202:Ñ 165 203:ⁿ 252 204:O 079 205:o 111 206:ô 147 207:ö 148 208:ò 149 209:Ö 153 210:ó 162 211:º 167 212:P 080 213:p 112 214:Q 081 215:q 113 216:R 082 217:r 114 218:S 083 219:s 115 220:ß 225 221:T 084 222:t 116 223:U 085 224:u 117 225:û 150 226:ù 151 227:ú 163 228:ü 129 229:Ü 154 230:V 086 231:v 118 232:W 087 233:w 119 234:X 088 235:x 120 236:Y 089 237:y 121 238:ÿ 152 240:z 122 

Ordenar ordena letras maiúsculas antes de minúsculas. (EDIT - Acabei de ler a ajuda para SORT e aprendi que não diferencia entre letras maiúsculas e minúsculas. O fato de que minha saída SORT consistentemente coloca upper antes lower é provavelmente um resultado da ordem da input.) Mas regex aparentemente classifica minúsculas antes de maiúsculas. Todos os seguintes intervalos não correspondem a nenhum caractere.

 >findstr /nrc:"^[Aa]" sortedChars.txt >findstr /nrc:"^[Bb]" sortedChars.txt >findstr /nrc:"^[Cc]" sortedChars.txt >findstr /nrc:"^[Dd]" sortedChars.txt 

Invertendo o pedido encontra os personagens.

 >findstr /nrc:"^[aA]" sortedChars.txt 151:A 065 152:a 097 >findstr /nrc:"^[bB]" sortedChars.txt 163:B 066 164:b 098 >findstr /nrc:"^[cC]" sortedChars.txt 165:C 067 166:c 099 >findstr /nrc:"^[dD]" sortedChars.txt 169:D 068 170:d 100 

Existem caracteres adicionais que regex classificam diferentemente de SORT, mas eu não tenho uma lista precisa.

Então, se você quiser

  • apenas números: FindStr /R "^[0123-9]*$"

  • octal: FindStr /R "^[0123-7]*$"

  • hexadecimal: FindStr /R "^[0123-9aAb-Cd-EfF]*$"

  • alpha sem acento: FindStr /R "^[aAb-Cd-EfFg-Ij-NoOp-St-Uv-YzZ]*$"

  • alfanumérico: FindStr /R "^[0123-9aAb-Cd-EfFg-Ij-NoOp-St-Uv-YzZ]*$"

Isso parece ser causado pelo uso de intervalos em pesquisas de expressões regulares.

Não ocorre para o primeiro caractere no intervalo. Não ocorre de todo para não-intervalos.

 > echo a | findstr /r "[AC]" > echo b | findstr /r "[AC]" b > echo c | findstr /r "[AC]" c > echo d | findstr /r "[AC]" > echo b | findstr /r "[BC]" > echo c | findstr /r "[BC]" c > echo a | findstr /r "[ABC]" > echo b | findstr /r "[ABC]" > echo c | findstr /r "[ABC]" > echo d | findstr /r "[ABC]" > echo b | findstr /r "[BC]" > echo c | findstr /r "[BC]" > echo A | findstr /r "[AC]" A > echo B | findstr /r "[AC]" B > echo C | findstr /r "[AC]" C > echo D | findstr /r "[AC]" 

De acordo com a página SS64 CMD FINDSTR (que, em uma exibição impressionante de circularidade, faz referência a essa pergunta), o intervalo [AZ] :

… inclui o alfabeto inglês completo, tanto maiúsculas e minúsculas (exceto “a”), como também caracteres alfabéticos não ingleses com diacríticos.

Para contornar o problema em meu ambiente, simplesmente usei expressões regulares específicas (como [ABCD] vez de [AD] ). Uma abordagem mais sensata para aqueles que são permitidos seria baixar o CygWin ou o GnuWin32 e usar o grep de um desses pacotes.

Todo mundo acima está errado. A ordem dos caracteres alfa é a seguinte: aAbBcCdDeE..zZ so echo a | findstr /r "[AZ]" echo a | findstr /r "[AZ]" não retorna nada, pois está fora desse intervalo.

echo abc|findstr /r "[AZ][AZ][AZ]" também não retorna nada, desde que o primeiro grupo corresponda ao b , o segundo corresponda ao c e o terceiro não corresponda a nada e, assim, todo o padrão regex não encontre nada.

Se você gosta de combinar com qualquer caractere de alfabeto latino – use [aZ] .