テンプレートとして使用するテキストファイルがあります。
Hostname : $HOSTNAME
Host Address : $HOSTADDRESS
私のbashスクリプトは2つの変数を設定し、HOSTNAME
テンプレートHOSTADDRESS
ファイルを読み、次に実行してandを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
出力をそのステップにパイプする必要があるためです。とても近いような気がします。
答え1
まず、代替は子プロセス(開始プロセス)xargs
で実行されるため、ここでは機能しません。ただし、シェル変数ではなく環境変数のみが子プロセスに渡されます。明らかに、スクリプトの起動時にスクリプト環境にすでに存在しますが、そうではありません。この時点ですべての変数をエクスポートしたい誘惑を受け取ることができますが、それでも多くの引用問題に直面するので、良い解決策ではありません。テンプレートにスペースが含まれている場合、またはスペースを保存したい場合は問題が発生します。bash
xargs
HOSTNAME
HOSTADDRESS
xargs
\"'
さて、現在のコードを見てください。参照の問題を除いて、MESSAGE=`eval echo $TEMPLATE`
コードを書く方法は複雑です。eval MESSAGE=$TEMPLATE
そして引用の問題があります。たとえば、すべてのスペースが縮小されていることがわかります。あなたは聞いたバービーテーブル、そうではありませんか?シェル拡張ルールは複雑ですが、気をつけるためのいくつかのルールがあります。
"$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)
)を持っています。
シェルスクリプトにテンプレートを含めるには、次のように自然に使用できます。トレドック。
MESSAGE=$(cat <<EOF)
Hostname : $HOSTNAME
Host Address : $HOSTADDRESS
EOF
ただし、外部テンプレートを使用するには2段階の評価を実行する必要があるため、eval
内部heredocsなどのファンキーな操作ではbashの解析を使用することが非常に問題になりますeval
。少なくともbash 4を使用することは間違いなくできる方法がありますが、チャンスをつかむことはお勧めできません。
答え2
シェルが変数置換を適用したいと思います。つまり、テンプレートテキストはシェル自体のコマンドの一部として読み取る必要がありますが、テンプレートも複数行テキストであるため、通常は行ベースの処理に入るのが少し面倒です。 UNIXコマンドで実行されます。
1つの方法はbashを使用することです。ここのドキュメント(シェルプロンプトに入力されたすべてのコマンドが表示されます):
テンプレートファイルの作成:
$ cat template.txt Hi, $NAME Welcome to $PLACE
出口テンプレートテキストで置き換える必要がある変数:
$ export NAME=Bob $ export PLACE='unix&linux'
bashで単一の「改行」
\n
文字を含むシェル変数を作成します。最も簡単な方法は、文字列を開き、改行文字を入力して閉じることです。$ newline=' > '
最後に、bashを呼び出して交換を実行します。
$ bash -c "cat <<__EOF__${newline}$(cat template.txt)${newline}__EOF__" Hi, Bob Welcome to unix&linux
これがうまくいくのはなぜですか?下から上に分解してみましょう。 bashにコマンドを実行するように依頼します(options経由-c
)。ただし、変数置換${newline}
とコマンド拡張は、$(cat ...)
実際の実行前に親シェルで発生します。bash -c ...
したがって、結果は、bash -c
改行を含むコマンド文字列と完全なテンプレートテキストを表示することです。これは、Bashプロンプトで次のように入力したのと同じです。
cat <<__EOF__
...contents of template.txt...
__EOF__
また、代替変数は親シェルに割り当てられているためエクスポートする必要がありますが、置換を実行するのは「子」bashです。
ノートただし、このアプローチは引用の問題に簡単につながる可能性があります。テンプレートテキストは bash シェルによって解釈されるので、その後脱落拡張され、システムでコマンドを実行できます。
答え3
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
答え4
MESSAGE=$(eval $(cat template.txt))