次のコマンドがあります。
find stdlib/main -type f -exec sh -c "echo {} | sed -e 's/stdlib\/main\///g' -e 's/\.q//g' -e 's/\//\./g' -e 's~^~/resource:\"{},~g' -e 's/$/\"/g'" \;
目的は、stdlib/main
(およびサブディレクトリ)ですべてのファイルを見つけ、次のようにフォーマットすることです。{filename},{filename-with-stdlibmain-removed-and-extension-removed-and-slashes-changed-to-dots}
直接コマンドを実行すると、コマンドは完全に実行されます。しかし、私はメイクファイルで使用しようとしています。
STDLIB_RESOURCES=$(shell find stdlib/main -type f -exec sh -c "echo {} | sed -e 's/stdlib\/main\///g' -e 's/\.neo//g' -e 's/\//\./g' -e 's~^~/resource:\"{},~g' -e 's/$/\"/g'" \;)
makefileを実行すると、見つかったすべてのファイルに対して次のいずれかのエラーが発生します。
sed: -e expression #5, char 5: unterminated `s' command
私がここで何を見逃しているのでしょうか?
答え1
あなたが見逃している最も重要なのは$
特殊文字を作ることであり、MakeとShellの引用が異なります。
例えば
's/$/\"/g'
``内部のすべてをシェルで保護しますが(\
しかし不要な作業も実行します)、そうではないので、次のように見えます。
's/\"/g'
名前付き変数がないと仮定します/
(makeでは可能ですが、通常はシェルでは不可能です)。
最初にやるべきこと$
はに置き換えることです$$
。
答え2
{}
実行するインラインスクリプト内で使用されるのは、find
コード注入の脆弱性です。しないでください。sh -c
(スクリプトの)最初の引数は一重引用符で囲み、スクリプトの引数をコマンドラインに渡す必要があります。
代わりに、find
次のようにコマンドを作成します(1つの場所で使用できないbash
代わりに使用)。sh
${parameter//pattern/word}
find stdlib/main -type f -exec bash -c '
for pathname do
string=${pathname#stdlib/main/} # delete initial path
string=${string%.*} # delete suffix after last dot
string=${string////.} # change slashes to dots
# output:
printf "resource:\"%s,%s\"\n" "$pathname" "$string"
done' bash {} +
これは使用するのではなく、sed
パラメータ置換を使用して見つかったパス名を変更することですfind
。インラインbash
スクリプトは見つかったファイルバッチで実行され、各バッチのパス名を繰り返します。printf
コマンドが実行されるのと同じ方法で変換されたデータを出力します。 (sed
私がそれを正しく解読した場合、それはそうではありません。ただあなたが説明するもの)。
後で二重引用符とコンマを含むファイル名を処理する方法は別の質問です(出力文字列は後で解析するのが難しい場合がありますresource:
)。
最も簡単な方法は、find
コマンドを別のスクリプトに入れてmake
GNUから呼び出すことです$(shell ...)
。それ以外の場合は、次のような結果が出ます。
STDLIB_RESOURCES := $(shell \
find stdlib/main -type f -exec bash -c ' \
for p do \
s=$${p\#stdlib/main/}; \
s=$${s%.*}; \
s=$${s////.}; \
printf "resource:\"%s,%s\"\n" "$$p" "$$s"; \
done' bash {} + )
make
また、(GNUが変数を処理する方法などのため)、Makefileが:=
変数にアクセスするたびにではなく、変数に割り当てられたときにすぐにこのコマンドを実行する必要があることに注意してください。
関連: