テンプレートとして使用しているテキストファイルがあります。次のようになります。
Hostname : $HOSTNAME
Host Address : $HOSTADDRESS
私のbashスクリプトは、HOSTNAME
とHOSTADDRESS
の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出力をパイプする必要があるためです。私はとても近くにいるように感じます。
まず、置換はサブプロセス(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 (シェルプロンプトで入力されたとおりに表示されるすべてのコマンド)を使用することです。
テンプレートファイルを作成します。
_$ cat template.txt
Hi, $NAME
Welcome to $PLACE
_
export テンプレートテキストで置換する必要のある変数:
_$ export NAME=Bob
$ export PLACE='unix&linux'
_
単一の「改行」_\n
_文字を保持するシェル変数を作成します。 bashで最も簡単な方法は、文字列を開き、改行を入力して閉じることです。
_$ newline='
> '
_
最後に、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 =展開されているため、システムでコマンドが実行される可能性があります。
実験のために、埋め込まれた一重引用符で囲まれた文字列を(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