파이프 명령에 대한 평가 제한 사항

파이프 명령에 대한 평가 제한 사항

변수에 파이프된 명령의 긴 체인을 구축하고 eval을 사용하여 실행하는 쉘 스크립트가 있습니다(다음 코드는 필요에 따라 단순화되었습니다).

 cmd="cat /some/files | grep -v \"this\" | grep -v \"that\""
 cmd="$cmd | grep -v \"much more dynamical filter with variables\""
 ...
 result=`eval $cmd`

지금까지는 괜찮았지만 이제 cmd 변수의 내용이 한도를 초과한 것 같습니다. 약 95970바이트를 초과하면 오류가 발생합니다(구문은 정확하지만).

eval: line ...: syntax error near unexpected token `|'

나는 몇 가지 조사를 했지만 어떤 단서도 얻지 못했습니다(getconf ARG_MAX는 2621440을 에코했고 ulimit -a도 나에게 도움이 되지 않았습니다).

이 제한이 어느 정도인지, 어떻게 늘리는지, 또는 이를 방지하는 가장 좋은 방법이 무엇인지 설명할 수 있습니까?


편집: 이제 지정된 스크립트를 사용하여 세 개의 다른 서버(centos)에서 테스트했습니다. 모든 서버에서 eval을 사용하여 한 명령으로 3333개의 파이프에 도달했습니다.

내가 찾은다른 페이지같은 일을 겪었지만 평가를 받지 못한 사람이 있나요? 그래서 이것은 단지 파이프라인의 한계인 것 같습니다.

파이프 수로 인해 제한이 발생할 수 있다는 것을 알면 문제를 해결하는 데 도움이 될 것입니다. 따라서 이것은 더 이상 문제가 되지 않습니다.

그러나 나는 이 제한을 설정하는 방법이나 최소한 스크립트를 실행하지 않고 제한 값을 감지하는 방법(아마도 모든 시스템 3333에서는 아닐 수도 있음)에 여전히 관심이 있습니다.

다음을 사용하여 복사할 수 있습니다.

yes cat | head -n 3334 | paste -sd '|' - | bash

답변1

여기서 문제는 실제로 파서의 문제입니다 bash. 편집하고 다시 컴파일하는 것 외에는 해결 방법이 없으며 bash3333 제한은 모든 플랫폼에서 동일할 것입니다.

bash 파서는 yacc(또는 일반적으로 모드 bison로 ) yacc구축 됩니다. yacc파서는 푸시다운 스택이 있는 유한 상태 기계를 구축하기 위해 LALR(1) 알고리즘을 사용하는 상향식 파서입니다. 대략적으로 말하면 스택에는 아직 축소되지 않은 모든 기호와 기호를 줄이기 위해 사용할 프로덕션을 결정하는 데 충분한 정보가 포함되어 있습니다.

이 유형의 파서는 왼쪽 재귀 문법 규칙에 최적화되어 있습니다. 표현식 구문의 맥락에서 왼쪽 재귀 규칙은 왼쪽 결합 연산자에 적용됩니다.-두번째일반 수학에서는. 이것은 다음과 같은 표현 때문에 연관 관계로 남습니다.-두번째-그룹이 왼쪽("연관")이 되도록 (-두번째)−c 대신−(두번째-). 대조적으로, 지수화는 우결합이므로두번째관례적으로 다음과 같이 평가됩니다.(두번째) 대신에 (두번째).

bash연산자는 산술적이기보다는 절차적입니다. 여기에는 단락 부울( &&and ||) 및 파이프( |and |&)는 물론 정렬 연산자 ;및 가 포함됩니다 &. 수학 연산자와 마찬가지로 대부분의 연산자는 왼쪽과 연결되지만 파이프 연산자는 오른쪽과 연결되므로 AND와 cmd1 | cmd2 | cmd3반대로 구문 분석됩니다 . (대부분의 경우 그 차이는 크지 않지만 관찰할 수 있습니다. [참고 1 참조])cmd1 | { cmd2 | cmd3 ; }{ cmd1 | cmd2 ; } | cmd3

일련의 왼쪽 연관 연산자로 구성된 표현식을 구문 분석하려면 작은 파서 스택만 필요합니다. 연산자를 클릭할 때마다 왼쪽의 표현식을 축소(원하는 경우 괄호 추가)할 수 있습니다. 대조적으로 일련의 오른쪽 결합 연산자로 구성된 표현식을 구문 분석하려면 표현식 끝에 도달할 때까지 모든 기호를 파서 스택에 배치해야 합니다. 그 이후에만 축소(괄호 삽입)가 시작될 수 있기 때문입니다. (이 설명은 기술적인 내용이 아니기 때문에 약간의 손짓이 필요하지만 실제 알고리즘이 작동하는 방식을 기반으로 합니다.)

Yacc 파서는 런타임에 파서 스택의 크기를 조정하지만 컴파일 시간 최대 스택 크기는 기본적으로 10000 슬롯입니다. 스택이 최대 크기에 도달한 경우 스택을 확장하려고 하면 메모리 부족 오류가 발생합니다. |올바른 접속사이므로 그 표현은 다음과 같습니다 .

statement | statement | ... | statement 

이 오류는 결국 발생합니다. 명백한 방법으로 구문 분석하면 5,000개의 파이프 기호(5,000개의 명령문 포함) 이후에 이런 일이 발생합니다. 그러나 파서가 개행을 처리하는 방식으로 인해 bash사용되는 실제 구문은 (대략) 다음과 같습니다.

pipeline: command '|' optional_newlines pipeline

결과적으로 optional_newlinesEvery 뒤에는 구문 기호가 나오 |므로 각 파이프는 3개의 스택 슬롯을 차지합니다. 따라서 3,333개의 파이프 기호 이후에 메모리 부족 오류가 발생합니다.

yyerror("memory exhausted")yacc 파서는 스택 오버플로를 감지하고 신호를 보냅니다 . 이는 . 그러나 bash구현에서는 yyerror제공된 오류 메시지를 삭제하고 "예기치 않은 토큰 근처에서 구문 오류가 감지되었습니다..."와 같은 메시지로 대체합니다. 이런 경우에는 좀 혼란스럽습니다.


노트

  1. 연관성의 차이는 |&stderr 및 stdout을 파이프하는 연산자를 사용하면 가장 쉽게 관찰할 수 있습니다. (또는 더 정확하게는 파이프를 설정한 후 stdout을 stderr에 복사합니다.) 간단한 예로 foo현재 디렉터리에 해당 파일이 없다고 가정합니다. 그 다음에

    # 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
    

관련 정보