バックティック出力で引用符が異なるように処理されます。

バックティック出力で引用符が異なるように処理されます。

背景

findスペースを含むファイル名のリスト(リストを介して)をカスタムPythonスクリプトに渡したいと思います。したがって、find各結果の周りに引用符を追加するように設定しました。

find ./testdata -type f -printf "\"%p\" "

結果:

"./testdata/export (1).csv" "./testdata/export (2).csv" "./testdata/export (3).csv"

test.pyこの質問に答えるために、私のカスタムスクリプト()が次のことをしているとしましょう。

#!/usr/bin/python3
import sys 


print(sys.argv)

観察結果

ケース1:

引用符付きパラメーターを手動でリストします。

入力する:./test.py "./testdata/export (1).csv" "./testdata/export (2).csv" "./testdata/export (3).csv"

出力:['./test.py', './testdata/export (1).csv', './testdata/export (2).csv', './testdata/export (3).csv']

ケース2:

使用xargs

入力する:find ./testdata -type f -printf "\"%p\" " | xargs ./test.py

出力:['./test.py', './testdata/export (1).csv', './testdata/export (2).csv', './testdata/export (3).csv']

(つまり、出力は次のようになります。ケース1)

ケース3:

バックティックを使用してください。

入力する:./test.py `find ./testdata -type f -printf "\"%p\" "`

出力:['./test.py', '"./testdata/export', '(1).csv"', '"./testdata/export', '(2).csv"', '"./testdata/export', '(3).csv"']

2つのことが変更されました。

  • "./testdata/exportこれで(1).csv"2つの別々のパラメータになりました。
  • 引用はまだ議論の一部です。

質問

  1. バックティックバージョンが異なる動作をするのはなぜですか?

  2. バックティックで引用符を含める方法はありますか?つまり、xargs?と同じように動作します。

コメント

ここで何が起こっているのかは本当に想像できません。論理的な説明は、バックティックで示されるコマンド出力が1つの大きな引数として扱われることです。しかし、なぜ空白に分割されるのですか?

したがって、次の最良の説明は、スペースで区切られた各文字列が、引用符であるかどうかにかかわらず、別々の引数として扱われることです。そうですか?それでは、バックティックがなぜこのような奇妙な動作をするのでしょうか?私たちがほとんどの場合、欲しいものはそうではありません...

ベストアンサー1

したがって、次の最良の説明は、スペースで区切られた各文字列が、引用符であるかどうかにかかわらず、別々の引数として扱われることです。そうですか?

はい、例をご覧ください。https://mywiki.wooledge.org/WordSplittingそしてスペースやその他の特殊文字が原因でシェルスクリプトが停止するのはなぜですか?そしていつ二重引用符が必要ですか?

シェルは、引用符が拡張の結果ではなく、元のコマンドラインに表示された場合にのみ引用符を処理し、引用符自体は引用符で囲まれません。

それでは、バックティックがなぜこのような奇妙な動作をするのでしょうか?私たちがほとんどの場合、欲しいものはそうではありません...

まあ、変なのは相対的です。ある人が何らかの状況で望むものは、他の状況ではまったく望むものではないかもしれません。

しかし、次のことを考慮してください。

a="blah blah"
somecmd -f "$a"

仕組みは、somecmd変数に含まれる文字列をパラメータとして使用することですaその中に何が入っていても。これは、Pythonなどの「実際の」プログラミング言語で動作する方法と似ていますsubprocess.call(["somecmd", "-f", a])。シンプルで清潔で完全に安全です。変数に特殊文字がなくても混乱を招く可能性があります。

文字列がスクリプトの外部から出てくる、ファイルから読み取られた、ユーザーが入力した場合、またはファイル名拡張の結果である場合、これは重要です。

echo "Please enter a filename: "
read -r a
somecmd -f "$a"

拡張結果を引用符で処理する場合、対になっていない引用Don't stop me now.mp3符があるため、ファイル名を入力できません。

また、追加の拡張のためにすべての拡張結果を処理する必要がありますか?かなり不快なことをするようにa設定してください。$(rm -rf $HOME).txtこれは完全に有効なファイル名なので*.txt

拡張後は引用符とエスケープだけを処理し、追加の拡張は処理しないことを提案できるため、これはやや誇張されています。ペアリングされていない一重引用符はまだ問題であり、$(find -printf "\"%p\"")二重引用符を含むファイル名ではまだ機能しません。

このように機能することもできますが、マジックハン​​ドルが静かでないほど、事故の可能性が低くなります。(殻に関しては、とても健全で幸いだと思います。)


findしかし、あなたは正しいです。つまり、シェルから文字列リストを取得するための即時かつ直接的な方法がないことを意味します。これは実際にsys.argvPythonと同じように文字列リストとして欲しいものです。引用符ではありません。

次のことができます。

find -print0 | xargs -0 ./test.py

-print0findファイル名をNULバイトで区切り文字(改行ではなく)として印刷するように要求し、それが私たちに必要なものであることを-0伝えます。xargsこれは、ファイル名に含めることができない唯一のバイトがNULバイトであるために機能します。少なくともGNUとFreeBSDで見つけることができます-print0-0

またはBashから:

mapfile -d '' files < <(find -print0)
./test.py "${files[@]}"

これは、プロセス置換と配列に使用されるのと同じ NUL で区切られた文字列です。

または、Bash(shopt -s globstar)と同様の機能を持つ他のプログラムでファイル名以外の条件でフィルタリングする必要がない場合:

shopt -s globstar
./test.py ./testdata/**

**のように*ただ再帰的です。

または標準ツールを使用してください。

find -exec ./test.py {} +

findtest.pyこれは、ファイル名のリストを別の場所に渡さずに独自に実行するように要求することで、問題全体を解決します。ただし、リストをどこかに保存する必要がある場合は役に立ちません。+最後のことは、各ファイルに対して一度だけ実行されることに注意してください-exec ./test.py {} \;test.py

おすすめ記事