O problema aqui é, na verdade, um problema com o bash
parser. Não há alternativa além de editar e recompilar bash
, e o limite 3333 provavelmente será o mesmo em todas as plataformas.
O analisador bash é gerado com yacc
(ou, normalmente, com bison
, mas no modo yacc
). yacc
analisadores são analisadores bottom-up, usando o algoritmo LALR (1) que cria uma máquina de estados finitos com uma pilha de empilhamento. Falando livremente, a pilha contém todos os símbolos ainda não reduzidos, juntamente com informações suficientes para decidir quais produções usar para reduzir os símbolos.
Esses analisadores são otimizados para regras gramaticais recursivas à esquerda. No contexto de uma gramática de expressão, uma regra recursiva à esquerda se aplica a um operador associativo à esquerda, como um - b em matemática comum. Isso é deixado associativo porque a expressão um - b - c grupos ("associados") para a esquerda, tornando-o igual a ( a - b ) - c em vez de a - ( b - c ). Por contraste, a exponenciação é associativa à direita, de modo que a b c é por convenção avaliada como a ( b c ) em vez de ( a b ) c .
bash
operadores são operadores de processo, em vez de operadores aritméticos; estes incluem booleans de curto-circuito ( &&
e ||
) e pipes ( |
e |&
), bem como operadores de sequenciamento ;
e &
. Como os operadores matemáticos, a maioria deles associa-se à esquerda, mas os operadores de pipe associam-se à direita, de modo que cmd1 | cmd2 | cmd3
seja analisado como se fosse cmd1 | { cmd2 | cmd3 ; }
, em oposição a { cmd1 | cmd2 ; } | cmd3
. (Na maioria das vezes a diferença não é importante, mas é observável. [Veja Nota 1])
Para analisar uma expressão que é uma sequência de operadores associativos à esquerda, você precisa apenas de uma pequena pilha de analisadores. Toda vez que você apertar um operador, você pode reduzir (parênteses, se quiser) a expressão à esquerda dele. Por outro lado, analisar uma expressão que é uma sequência de operadores associativos à direita exige que você coloque todos os símbolos na pilha do analisador até chegar ao final da expressão, porque somente então você pode começar a reduzir (inserindo parênteses). (Essa explicação envolve um pouco de aceno de mão, uma vez que foi planejada para ser não-técnica, mas é baseada no funcionamento do algoritmo real.)
Os analisadores Yacc redimensionarão a pilha do analisador no tempo de execução, mas há um tamanho máximo de pilha em tempo de compilação, que por padrão é de 10.000 slots. Se a pilha atingir o tamanho máximo, qualquer tentativa de expansão causará um erro de falta de memória. Porque |
é associativo à direita, uma expressão da forma:
statement | statement | ... | statement
acabará por desencadear este erro. Se fosse analisado da maneira óbvia, isso aconteceria depois de 5.000 símbolos de pipe (com 5.000 instruções). Mas devido à maneira como o bash
analisador manipula as novas linhas, a gramática real usada é (aproximadamente):
pipeline: command '|' optional_newlines pipeline
com a conseqüência de que há um símbolo de% gramática optional_newlines
após cada |
, então cada canal ocupa três slots de pilha. Portanto, o erro de falta de memória é gerado após 3.333 símbolos de pipe.
O analisador yacc detecta e sinaliza o estouro de pilha, que ele chama chamando yyerror("memory exhausted")
. No entanto, a implementação bash
de yyerror
elimina a mensagem de erro fornecida e substitui uma mensagem como "erro de sintaxe detectado próximo ao token inesperado ...". Isso é um pouco confuso neste caso.
Notas
-
A diferença na associatividade é mais facilmente observada usando o operador
|&
, que canaliza stderr e stdout. (Ou, mais precisamente, duplica stdout em stderr depois de estabelecer o pipe.) Para um exemplo simples, suponha que o arquivofoo
não exista no diretório atual. Então# There is a race condition in this example. But it's not relevant. $ ls foo | ls foo |& tr n-za-m a-z ls: cannot access foo: No such file or directory yf: pnaabg npprff sbb: Nb fhpu svyr be qverpgbel # Associated to the left: $ { ls foo | ls foo ; } |& tr n-za-m a-z yf: pnaabg npprff sbb: Nb fhpu svyr be qverpgbel yf: pnaabg npprff sbb: Nb fhpu svyr be qverpgbel # Associated to the right: $ ls foo | { ls foo |& tr n-za-m a-z ; } ls: cannot access foo: No such file or directory yf: pnaabg npprff sbb: Nb fhpu svyr be qverpgbel