パターンと解析されたパスにスペースが含まれている場合にワイルドカード/グローバル拡張を実行する方法は?

パターンと解析されたパスにスペースが含まれている場合にワイルドカード/グローバル拡張を実行する方法は?

POSIX shまたはBashを使用していくつかのパスを拡張する必要があります。

以下は2つのパターン例です(複雑すぎるように選択しました)。

$ npm pkg get workspaces | jq -r '.[]'
apps/app*
lib/{be,fe *} lib/*lib

私のディレクトリツリーが次のようになるとしましょう。

$ mkdir -p "lib/be lib/fantastic lib" "lib/fantastic" "lib/fe 1 lib/other lib" "apps/app1" "apps/app2" "be" "1"

$ tree
.
├── 1
├── apps
│   ├── app1
│   └── app2
├── be
└── lib
    ├── be lib
    │   └── fantastic lib
    ├── fantastic
    └── fe 1 lib
        └── other lib

12 directories, 0 files

パターンに一致するすべてのパスの簡単なリスト(1行に1つのパス)を取得するにはどうすればよいですか?

デフォルトのシェル拡張は、個々のパスを引用せずにパスを解析し、スペースで区切るように見えます。

たとえば、この偶数の一致は何ですか?

$ echo "lib/"{"be","fe "*}" lib/"*"lib"
lib/be lib/fantastic lib lib/fe 1 lib/other lib

それは次のようにすることができますlib/be lib/fantasticliblib/fe 1lib/other lib
lib/be lib/fantastic liblib/fe 1 lib/other lib
lib/be lib/fantastic lib lib/fe 1 lib/other lib

どのスペースが区切り文字で、どのスペースがパスの一部かを知らない場合は、不明のようです。

しかし、同様に難しいのは、スペースを含むすべての項目を引用する必要がありますが、同時にワイルドカードなどを引用してはいけません。

私の言葉は、私が何かを一緒に投げることができましたが、これが実際に可能なすべてのケースを解決するかどうか疑問です。

echo 'lib/{be,fe *} lib/*lib' | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g'

両方のモードで実行すると動作するようです。

$ echo -e 'lib/{be,fe *} lib/*lib\napps/app*' | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g' | while IFS= read -r line; do bash -c "echo $line"; done
lib/be lib/fantastic lib lib/fe 1 lib/other lib
apps/app1 apps/app2

それでは、道はどこから始まり、どこで終わりますか?

最後にevalまたはbash -c。たとえば、同様のファイルパターンでbye && rm -rf ~ホームディレクトリを削除できます。

答え1

デフォルトのシェル拡張はパスを解析し、スペースで区切るように見えます。

それは愚かなことではなく、単に動作しません。ここで重要なのは、コマンドラインが処理されると、1つの長い文字列ではなく、さまざまな文字列(「単語」または「フィールド」)のセットのように処理されることです。中かっこ拡張とファイル名グローバルは、いくつかの異なるフィールドを生成します。これらのフィールドは、実行するすべてのコマンドのコマンドライン引数として使用されます(最終的にargv[]Cプログラムで通常呼び出される配列の要素として使用されます)。

問題であり、一般的なトラップは、echo取得するすべての引数をスペースで連結して、表示される長いリストを生成することです。

たとえば、Bash の相互作用は、help echoこれが正確に実行される操作であることを明示的に明示しています。

$ help echo
echo: echo [-neE] [arg ...]
    Write arguments to the standard output.

    Display the ARGs, separated by a single space character and followed by a
    newline, on the standard output.

これは、パラメータが明らかに異なる場合でも同じ出力を提供することを意味します。

$ echo foo bar doo
foo bar doo
$ echo "foo bar" doo
foo bar doo

しかし、このように単純なものを使用すると、どのようにls機能するかを確認できます。

$ touch "foo bar" doo
$ ls -l *oo*
-rw-r----- 1 ilkkachu ilkkachu 0 Sep  6 12:58 doo
-rw-r----- 1 ilkkachu ilkkachu 0 Sep  6 12:58 foo bar

echoglob 軸の出力をシェルにコピーすると、次のいずれかの結果が得られます。

$ ls -l foo bar doo
ls: cannot access 'foo': No such file or directory
ls: cannot access 'bar': No such file or directory
-rw-r----- 1 ilkkachu ilkkachu 0 Sep  6 12:58 doo

または

$ ls -l "foo bar doo"
ls: cannot access 'foo bar doo': No such file or directory

(文字列をスペースでさらに分割するかどうかによって異なります)

ここで解決策は、echoデバッグの使用を中止することです。代わりにprintf適切なオプションを使用してください。これは<、印刷と使用の間の異なるパラメータに対してフォーマット文字列を複数回再使用する必要があるという事実を考慮します。>printf

$ printf "<%s>\n" *oo*
<doo>
<foo bar>

または、次のスクリプトを作成します。

#!/bin/sh
printf "%d args\n" "$#"
if [ "$#" -gt 0 ]; then
    printf "<%s>\n" "$@"
fi

たとえばargs.sh。次に、スタンド拡張装置を試してみてください。

しかし、同様に難しいのは、スペースを含むすべての項目を引用する必要がありますが、同時にワイルドカードなどを引用してはいけません。

あなたは本当にこれから抜け出すことはできません。一部の文字は一方の面で特別であり(スペースは単語を分割します)、一部の文字は別の面で特別であり(glob文字はファイル名に拡張されます)、そのまま維持したいもの(glob文字)、維持したくない文字(スペース)。

最後に、evalまたはbash -cを使用して問題を解決する方法がわかりません。悪意のあるパターンがシステムを本質的に消去する可能性があるため、これはやや危険に見えます。

はい、危険ではありません。データをデータとして、コードをコードとして保持し、混在させないでください。ファイル名拡張は実際には分離を維持し、ワイルドカードを使用して任意の文字を含むファイル名を安全に処理できます。stdout問題は、複数のファイル名を単一の文字列または単一の出力ストリーム(たとえば、of)として印刷しようとしたときに発生しますecho。必要でない場合はこれを避け、そうする場合はファイル名をNULで終わる(Cスタイル)文字列で印刷してください。なぜならそれがNULだからです。

あなたの質問はトークン化(引用符なしのパラメータ拡張)に関するものではありませんが、まだ役に立つかもしれません。 https://mywiki.wooledge.org/WordSplitting

答え2

*などのワイルドカード文字を?引用すると、その特殊な意味は無効になります。ただし、スペースを保護するには引用またはエスケープする必要があります。解決策は、パターン内の必要な部分だけを引用またはエスケープし、ワイルドカード演算子を使用しないことです。たとえば、

1つ以上のスペースを含み、ピリオドで始まらない現在のディレクトリ内のすべてのオブジェクト:

  *" "*

もう1つの方法は、引用するのではなく、スペースを抜けることです。

  *\ *

Bash中括弧拡張はワイルドカードではありません。テキストを生成する理解力表記です。a{b,c}d{"a$x$d" | xϵ{ "b"、"c"}}を意味します。 $ x $のすべての文字列a $ x $ dは、「b」要素と「c」要素です。

Bashは最初に中括弧拡張を実行してフィールドを作成し、そのフィールドに対してパス名拡張を実行します。

引用符は中括弧の拡張を抑制します。中かっこは引用符を解除する必要があります。

同様のパターンが与えられると、*.{jpg,gif}中括弧拡張が最初に適用され、フィールドの*.jpg合計が生成されます*.gif。これにより、これらのファイルはコマンドラインにこのように入力されたかのようにファイル名拡張子が適用されます。

引用符とエスケープは中かっこ内に適用して、拡張されていないフィールドとを生成できます{\*,"?"}\*"?"*?

答え3

ありがとうコメント @ilkkatchu今、私はecho以外のものを使用する必要があることを理解しているので、受け取った各引数を標準出力に1行で印刷する簡単なインラインbashスクリプトを考えました。 printf "%s\n" "$0" "$@ " 次に、拡張パターンを「簡単に」渡します。

# Set up test directory structure
mkdir -p "lib/be lib/fantastic lib" "lib/fantastic" "lib/fe 1 lib/other lib" "apps/app1" "apps/app2" "be" "1"

# Define path patterns
export PATH_PATTERNS='lib/{be,fe *} lib/*lib
apps/app*'

# Print path patterns
echo -e "$PATH_PATTERNS"
# Output is:
# lib/{be,fe *} lib/*lib
# apps/app*

# Put double quotes around everything that is not `*`, `,`, `{` and `}`
export SANITIZED_PATH_PATTERNS="$(echo -e "$PATH_PATTERNS" | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g')"
echo -e "$SANITIZED_PATH_PATTERNS"
# Output is:
# "lib/"{"be","fe "*}" lib/"*"lib"
# "apps/app"*

# Iterate over every sanitized expression and expand it by evaluating it with bash -c "... $line",
# And inside that new bash put another bash -c "..." right before the $line, so that the expanded $line is passed as multiple parameters to the next bash. # In that next bash we simply print all passed arguments to stdout (on per line), by using `printf "%s\n" "$0" "$@"`:
echo -e "$SANITIZED_PATH_PATTERNS" | while IFS= read -r line; do 
    bash -c "bash -c 'printf \"%s\n\" \"\$0\" \"\$@\"' $line";
done
# Output is:
# lib/be lib/fantastic lib
# lib/fe 1 lib/other lib
# apps/app1
# apps/app2

または1行で:

$ echo "$PATH_PATTERNS" | sed -e 's/\([*,{}]\)/"\1"/g' -e 's/.*/"&"/' -e 's/""//g' | while IFS= read -r line; do bash -c "bash -c 'printf \"%s\n\" \"\$0\" \"\$@\"' $line"; done

残念ながら、マルウェアモードに関する質問に記載されているセキュリティ関連事項はまだ適用されており、これはPOSIXと互換性がなく、上記の2つのモードでのみテストされています。私のアプローチに問題を引き起こす可能性があるものは次のとおりです。

  • 改行文字を含むパターン
  • 改行文字を含む一致パス
  • 中かっこ定義の外側にカンマを含むパターン
  • エスケープされたワイルドカード文字を含むパターン\*
  • デュアルワイルドカード**
  • 疑問符を含むパターン

これらすべての問題を簡単に解決する方法があったらと思いますが、そうではありません。 Pythonや他の最新のスクリプトエンジンを使用できる場合は、その言語でスクリプトを作成してパターン解析を処理することをお勧めします。

または、次の既存のcliユーティリティを使用してください。全体的な状況次のようにインストールして使用できますnpm i -g glob

glob "apps/app*" "/{bin,usr/bin}/" "test/**"

フラグを使用すると、--cmd拡張モードを他のコマンドの引数として渡すこともできます。

関連情報