bash関数で "grep | grep"コマンドを文字列として実行するには?

bash関数で "grep | grep"コマンドを文字列として実行するには?

bash関数で、あるgrepコマンドの結果を別のgrepコマンドにパイプするコマンドを作成しようとしています。最終的に私が実行したいコマンドは次のとおりです。

grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:

私が作成している関数は、コマンドの最初の部分を文字列に保存してから2番目の部分を追加します。

grep_cmd="grep -I -r $pattern $@"

if (( ${#file_types[@]} > 0 )); then
    file_types="${file_types[@]}"
    file_types=.${file_types// /':\|.'}:

    grep_cmd="$grep_cmd | grep $file_types"
fi

echo "$grep_cmd"
${grep_cmd}

出力の最初の部分の後にエラーが発生します。

grep: |: No such file or directory
grep: grep: No such file or directory
grep: .c:\|.h:: No such file or directory

最後の行をからに変更すると、最初の部分の出力のみが${grep_cmd}表示され、他のエラーが発生します。"$grep_cmd"

bash: grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:: No such file or directory

すべてこの回答、最後の行を$(grep_cmd)。別のエラーが発生します。

bash: grep_cmd: command not found

この回答お勧めしますeval $grep_cmd。これはエラーを抑制するだけでなく、出力も抑制します。

これ推奨eval ${grep_cmd}結果は同じです(エラーと出力の抑制)。私はbashでデバッグを有効にしようとしました(使用してset -x)、次のような結果が得られました。

+ eval grep -I -r FooBar /code/internal/dev/ /code/public/dev/ '|' grep '.c:\|.h:'
++ grep -I -r FooBar /code/internal/dev/ /code/public/dev/
++ grep '.c:|.h:'

パイプがエスケープされているように見えるので、シェルはコマンドを2つのコマンドとして解釈します。パイプ文字を正しくエスケープしてコマンドとして解釈するにはどうすればよいですか?

答え1

コメントで述べたように、あなたが経験している多くの困難は、コマンドを変数に保存してから後でコマンドを実行しようとするためです。

コマンドを保存しようとする代わりに、すぐにコマンドを実行すると、はるかに良い幸運を享受できます。

たとえば、次は達成したいことをする必要があります。

if (( ${#file_types[@]} > 0 )); then
    regex="${file_types[*]}"
    regex="\.\(${regex// /\|}\):"
    grep -I -r "$pattern" "$@" | grep "$regex"
else
    grep -I -r "$pattern" "$@"
fi

答え2

シェルプログラミングについて覚えておくべき1つのことは、データには2つのタイプがあり、これはチュートリアルで明確に説明されていないことが多いことです。文字列と文字列のリスト。改行やスペース区切り文字を含む文字列とは異なり、文字列のリストはそれ自体です。

覚えておくべきもう1つのことは、ほとんどの拡張はシェルがファイルを解析するときにのみ適用されることです。コマンドの実行には拡張は含まれません。

一部の拡張は変数値で発生します。$fooつまり、「変数値を取得し、fooスペースを区切り文字として文字列リストに分割し、リスト内の各要素をワイルドカードパターンとして解釈してから展開します」を意味します。この拡張は、変数が呼び出しリストのコンテキストで使用されている場合にのみ発生します。文字列を必要とする文脈では、$fooこれは「変数値を取得するfoo」を意味します。二重引用符は文字列コンテキストを適用するため推奨されます。常に二重引用符内で変数置換とコマンド置換を使用してください"$foo""$(somecommand)"²。 (変数と同様に、保護されていないコマンド置換でも同じ拡張が行われます。)

解析と実行の違いにより、単にコマンドを文字列に入れて実行することはできません。を作成すると、${grep_cmd}解析ではなく分割とワイルドカードのみが発生するため、このような文字は特に|意味がありません。

シェルコマンドを文字列に入れる必要がある場合は、eval次のようにします。

eval "$grep_cmd"

二重引用符に注意してください。変数値にはシェルコマンドが含まれているため、正確な文字列値が必要です。しかし、このアプローチは複雑な傾向があります。実際、シェルのソース構文に何かがあるはずです。たとえば、ファイル名が必要な場合は、ファイル名を正しく引用する必要があります。したがって、そこに$patternandだけを入れることはできず、$@解析時にパターンを含む単一の単語とパラメータを含む単語のリストを生成する文字列を作成する必要があります。

結論として:シェルコマンドを変数に入れないでください。代わりに、機能の使用。パイプなどの複雑なコマンドの代わりにパラメータを持つ単純なコマンドが必要な場合は、配列を使用できます(配列変数は文字列のリストを格納します)。

これは可能なアプローチの1つです。run_grepあなたが示すコードには実際にこの関数は必要ありません。これは大きなスクリプトの小さな部分であり、その間にはより多くのコードがあると仮定してここに含めました。これが実際にスクリプト全体である場合は、パイプするターゲットの場所でgrepを実行してください。また、フィルタを書くためのコードを修正しましたが、これは正しくないようです(たとえば、.正規表現では、「すべての文字」は「すべての文字」を意味しますが、リテラルポイントが必要なようです)。

grep_cmd=(grep -I -r "$pattern" "$@")

if (( ${#file_types[@]} > 0 )); then
    regexp='\.\('
    for file_type in "${file_types[@]}"; do
      regexp="$regexp$file_type\\|"
    done
    regexp="${regexp%?}):"
    run_grep () {
      "${grep_cmd[@]}" | grep "$file_types"
    }
else
  run_grep () {
    "${grep_cmd[@]}"
  }
fi

run_grep

1より一般的には の値を使用しますIFS
²専門家のみ:二重引用符を使用しないことが正しい効果を持つ理由を理解していない限り、変数とコマンドの置換には常に二重引用符を使用してください。
専門家のみ:シェルコマンドを変数に入力する必要がある場合は、引用に注意してください。


あなたがやっていることは過度に複雑で信頼できないようです。インクルードファイルがある場合はどうですかfoo.c: 42? GNU grepには、--include再帰パトロールで特定のファイルのみを検索するオプションがあります。ただ使用してください。

grep_cmd=(grep -I -r)
for file_type in "${file_types[@]}"; do
  grep_cmd+=(--include "*.$file_type")
done
"${grep_cmd[@]}" "$pattern" "$@"

答え3

command="grep $regex1 filelist | grep $regex2"
echo $command | bash

関連情報