変数にパイプされたコマンドの長いチェーンを構築し、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も私には役に立ちませんでした)。
この制限がどれくらいであるか、どのように増やすのか、またはこれを防ぐ最良の方法が何であるかを説明できますか?
編集:これで、指定されたスクリプトを使用して3つの異なるサーバー(centos)でテストされました。すべてのサーバーでevalを使用して、1つのコマンドで3333のパイプに到達しました。
私が見つけたその他のページ同じことを経験したが評価を受けていない人はいますか?だからこれは単にパイプラインの制限のようです。
パイプの数によって制限が発生する可能性があることを知っていると、問題を解決するのに役立ちます。したがって、これはもはや問題ではありません。
しかし、私はこの制限を設定する方法、または少なくともスクリプトを実行せずに制限値を検出する方法(おそらくすべてのシステム3333ではないかもしれません)にまだ興味があります。
以下を使用してコピーできます。
yes cat | head -n 3334 | paste -sd '|' - | bash
答え1
ここでの問題は実際にパーサーの問題ですbash
。編集して再コンパイルする以外に回避策はなく、bash
3333の制限はすべてのプラットフォームで同じです。
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_newlines
Everyの後に構文記号が現れる|
ので、各パイプは3つのスタックスロットを占めます。したがって、3,333 個のパイプシンボルの後にメモリ不足エラーが発生します。
yyerror("memory exhausted")
yaccパーサーはスタックオーバーフローを検出して信号を送信します。これは 。ただし、bash
実装はyyerror
提供されたエラーメッセージを削除し、「予期しないトークンの近くで構文エラーが検出されました...」などのメッセージに置き換えます。このような場合は少し混乱しています。
ノート
関連性の違いは、
|&
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