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 arquivofoonã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