web-dev-qa-db-ja.com

evalエコーで使用するためにパイプライン値をパラメーターとしてxargsに渡す

テンプレートとして使用しているテキストファイルがあります。次のようになります。

Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS

私のbashスクリプトは、HOSTNAMEHOSTADDRESSの2つの変数を設定し、テンプレートファイルを読み取り、evalを実行して$HOSTNAME$HOSTADDRESSを展開します。

HOSTNAME="SH_SQL_0089"
HOSTADDRESS="172.16.3.44"
TEMPLATE=`cat template.txt`
MESSAGE=`eval echo $TEMPLATE`

MESSAGEの結果の値は次のとおりです。

Hostname     : SH_SQL_0089
Host Address : 172.16.3.44

最後の2行を次のように減らすことは可能ですか?

MESSAGE=$(cat template.txt | eval echo ????)

xargsを使ってみました:

MESSAGE=$( cat template.txt | xargs -i bash -c "eval echo {}" )

ただし、$HOSTNAMEを置き換えるだけで、キャリッジリターンを取り除きます。

これを実行したい理由は、3番目の処理ステップを追加し、それにeval 'd出力をパイプする必要があるためです。私はとても近くにいるように感じます。

3
Kev

まず、置換はサブプロセス(xargsが起動するbashプロセス)で実行されるため、xargsはここでは機能しません。ただし、シェル変数ではなく、環境変数のみがサブプロセスに渡されます。明らかに、HOSTNAMEはスクリプトの開始時にすでにスクリプトの環境にありましたが、HOSTADDRESSはそうではありません。この時点で、すべての変数をエクスポートしたくなるかもしれませんが、それでもxargsは、引用の問題が多数あるため、適切な解決策ではありません。テンプレートに_\"'_が含まれている場合、または保持したい場合空白、あなたは乾杯です。

ここで、現在のコードを見てみましょう。_MESSAGE=`eval echo $TEMPLATE`_は、引用の問題を除いて、_eval MESSAGE=$TEMPLATE_を記述する複雑な方法です。そして、あなたには引用の問題があります。たとえば、すべての空白が折りたたまれていることに気付くでしょう。 Bobby Tables と聞いたことがありますか?シェル拡張ルールはかなり複雑ですが、正気を保つルールがいくつかあります。

  • 変数置換_"$foo"_およびコマンド置換"$(bar)"は常に二重引用符で囲みます。引用符を省略する必要がある理由を理解している場合は、このルールに違反する可能性があります。
  • コマンド置換には、_`…`_ではなく$(…)を使用してください。バッククォート形式内での引用は難解で移植性がありませんが、$(…)内での引用は正常に機能します。
  • ガンポイントで押された場合にのみevalを使用します。その場合は、細心の注意を払い、できるだけシンプルにしてください。

では、_eval MESSAGE="$TEMPLATE"_で何がうまくいかないのでしょうか。シェルに評価させます

_MESSAGE=Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS
_

MESSAGEの値であるはずの部分を引用符で囲む必要があります。引用符はevalに到達する必要があるため、文字通りシェル拡張の最初の段階である_eval MESSAGE="\"$TEMPLATE\""_を通過する必要があります。

_MESSAGE="Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS"
_

より良いですが、これでボビーテーブルのインジェクションパターンが正確に得られました。テンプレートに見積もりが含まれている場合はどうなりますか?次に、引用符をエスケープする必要があります。二重引用符で囲まれた特別な意味を持つ4文字は_\"$`_であり、_$_にその意味を保持させたいので、他の3文字の前に円記号を追加します。

_TEMPLATE=$(sed -e 's/[\\"`]/\\&/g' <template.txt)
eval MESSAGE="\"$TEMPLATE\""
_

今シェルは評価します

_MESSAGE="Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS
Name         : Bobby \"drop\" O'Tables"
_

そして、すべてが良いです。

ここでの適切な引用は、偶発的な構文解析の問題から保護するためのものであることに注意してください。テンプレートを制御する人は誰でもシェルアクセス権を持ちます($(hello)を使用)。

シェルスクリプトにテンプレートを含めたい場合、これは当然 heredoc で行われます。

_MESSAGE=$(cat <<EOF)
Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS
EOF
_

ただし、外部テンプレートを使用する場合は、2段階の評価を行う必要があるため、evalを使用します。また、eval内のヒアドキュメントのようなファンキーなものに関しては、bashの解析はかなりバグがあります。少なくともbash4で機能する方法は確かにありますが、リスクを冒すことはお勧めしません。

シェルで変数の置換を適用する必要があります。つまり、テンプレートテキストはコマンドの一部としてシェル自体で読み取る必要がありますが、テンプレートも複数行のテキストであるため、行指向に入るのは少し注意が必要です。通常、UNIXコマンドによって実行される処理。

1つのアプローチは、bash here-documents (シェルプロンプトで入力されたとおりに表示されるすべてのコマンド)を使用することです。

  1. テンプレートファイルを作成します。

    _$ cat template.txt
    Hi, $NAME
    
    Welcome to $PLACE
    _
  2. export テンプレートテキストで置換する必要のある変数:

    _$ export NAME=Bob
    $ export PLACE='unix&linux'
    _
  3. 単一の「改行」_\n_文字を保持するシェル変数を作成します。 bashで最も簡単な方法は、文字列を開き、改行を入力して閉じることです。

    _$ newline='
    > '
    _
  4. 最後に、bashを呼び出して置換を行います。

    _$ bash -c "cat <<__EOF__${newline}$(cat template.txt)${newline}__EOF__"
    Hi, Bob
    
    Welcome to unix&linux
    _

なぜこれが機能するのですか?下から分解してみましょう。bashにコマンドを実行するように要求していますが(_-c_オプションで)、変数置換_${newline}_とコマンド展開$(cat ...)が_bash -c ..._が実際に実行される前の親シェル。その結果、_bash -c_には、改行とテンプレートテキスト全体を埋め込むコマンド文字列が表示されます。これは、bashプロンプトで次のように入力したかのようです。

_cat <<__EOF__
...contents of template.txt...
__EOF__
_

また、変数は親シェルで割り当てられるため、置換変数をエクスポートする必要がありますが、置換を行うのは「子」bashであることに注意してください。

ただし、このアプローチでは引用の問題が発生しやすいことに注意してください。テンプレートテキストはbashシェルによって解釈されるため、 backtics =展開されているため、システムでコマンドが実行される可能性があります。

2
Riccardo Murri

実験のために、埋め込まれた一重引用符で囲まれた文字列を(double)evalすることもできます。

export HOSTNAME HOSTADDRESS
(
NL='
'
DELIM=$'\177'
esc="'\''"
export IFS=""
HOSTNAME="SH_SQL_00'8'9"
HOSTADDRESS='172.16.3.44 Bobby "drop" O'\''Tables ${PWD} $(ls)'
printf '%s\n' 'Hostname : $HOSTNAME' 'Host Address : $HOSTADDRESS' > template.txt
TEMPLATE="$(<template.txt)"
TEMPLATE="${TEMPLATE//\'/${esc}}"       # escape every single quote: ' --> '\'' (which later will become ''\''' by '${TEMPLATE}')
TEMPLATE="${TEMPLATE//${NL}/${DELIM}}"  # replace every newline char with a delim char
evalstr="echo '${TEMPLATE}'"
#printf '\n%s\n\n' "${evalstr}" | LC_ALL=C vis -fotc
set -xv
eval eval "${evalstr}"
) | LC_ALL=C tr '\177' '\n' | nl
0
tim