findとsedを使用してファイル名を取得する方法

findとsedを使用してファイル名を取得する方法

私は特定のファイルにsedを適用し、変更されたファイルを一覧表示して、どのファイルが変更されたかを知ることができるスクリプトを作成しています。

これが私がsedを見つけて使用した方法です。

find . -type f -a \( -name "*.txt" -o -name "*.git"\) -a -exec sed -i -e "s/"str1"/"str2"/g" {} +

変更されたファイルのファイル名をどのように出力しますか?読みやすく整列した順序で印刷したいです。

sedのみを使用している場合は、次のことができます。

sed -i 's/$pattern/$new_pattern/w changelog.txt' $filename
if [ -s changelog.txt ]; then
  # CHANGES MADE, DO SOME STUFF HERE
else
  # NO CHANGES MADE, DO SOME OTHER STUFF HERE
fi

しかし、findとsedを同時に使用する場合はどうすればよいですか?マニュアルページをチェックしてたくさん試してみましたが、何も機能しませんでした。

答え1

sed -isスクリプトのコマンドがsed成功したかどうかにかかわらず、ファイルを再作成します(効果的にファイルの完全な新しいコピーを作成します)。

sed -iここでは、.:を含めずにGNUツールを使用したくないとしますstr1

find . -type f \( -name "*.txt" -o -name "*.git" \) -size +3c \
  -exec grep -lZ str1 {} + |
  while IFS= read -rd '' file; do
    sed -i 's/str1/str2/g' "$file" &&
      printf '%s\n' "$file"
  done

成功した場合(新しいバージョンのファイルの作成中にエラーは発生しません)、ファイル名を含むsed各ファイルに対して1つずつ実行し、str1ファイル名を印刷します。sed

または、ファイルごとにgrep1つずつ実行できますsed

find . -type f \( -name "*.txt" -o -name "*.git" \) \( -size +3c \
  -exec grep -q str1 {} \; \
  -exec sed -i 's/str1/str2/g' {} \; \
  -printf '"%p" was modified\n' \
    -o -printf '"%p" was not modified\n"' \)

答え2

あなたのsed命令(正確に引用):

sed 's/str1/str2/g'

str1これにより、すべての項目がに変わりますstr2。含まれているファイルのリストはstr1以下から入手できますgrep -l 'str1'

find . -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec grep -l 'str1' {} \; \
    -exec sed -i 's/str1/str2/g' {} + >changelist.txt

ここではgrep -lリダイレクトが提供されます。これは、そのパターンを含むファイルでのみ実行されるフィルタとしても機能changelist.txtします。これにより、変更がファイルに適用されます(静かに残ります)。sedsedsed -i

または、find文字列を含むファイルのパス名を印刷してみましょう。

find . -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec grep -q 'str1' {} \; \
    -print \
    -exec sed -i 's/str1/str2/g' {} + >changelist.txt

関連:

答え3

私がこの質問に答える方法は次のとおりです。 先行は達成するのが難しい楽しみのために。だからあなたがこれが好きなら、あなたは彼に投票する必要があります。これは、 find を一度だけ呼び出しながら複数のコマンドを実行し、より多くの複雑さを処理する方法を示すため、少し異なります。

回答

一致するものが見つかると、GrepはTrueと評価されます(例:$?== 0)。したがって、grep -l 'str1' filenameファイル名にstr1がある場合も同様です。このコマンドをsedコマンドに接続すると、&&grepが一致した場合にのみsedを実行できるようになります。

次のコマンドは、sed が変更を希望する場合にのみファイル名を出力します。

grep -l 'str1' filename && sed -i 's/str1/str2/g' filename

-execでは直接使用できないため、&&bash呼び出しでラップします。

find ./ -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec bash -c "grep -l 'str1' {} && sed -i 's/str1/str2/g' {}" \; > changelist.txt

これと Kusalananda の答えの間の明らかな違いは、grep が str1 と一致しない場合、sed が実行されないことです。 Kusalanandaの答えでは、grepは各ファイルに対して実行され、sedは各ファイルに対して実行されます。ファイルの数によっては、実行時間に大きな影響を与える可能性があります。 OPの質問によると、おそらく全く大きな違いはありません。

あなたできるgrep -qに置き換えgrep -l+に置き換え、削除して、\;彼の答えを簡素化します-print

find . -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec grep -l 'str1' {} \; \
    -exec sed -i 's/str1/str2/g' {} \; >changelist.txt

これらすべては単なる叱責です。次のステップはbash -cin find-execオプションを使用する理由です。誰かが役に立つと思います。

私が近づく理由

私はsedをfindと一緒に使用してログファイルの一部を印刷し、sedが何でも出力する場合にのみログファイル名を印刷したいので、ここに来ました。

次の内容を含むログがあります。

    ---- lots of lines before ----
Failed:    0

--------------------------------------------------
Summary
--------------------------------------------------
( Cases/Passed/Failed)
Frequency Test           : (    69/    67/     2)
Carrier/Data Null Test   : (    14/    13/     1)
Total Harmonic Distortion: (     9/     9/     0)
Spur Test                : (     0/     0/     0)

--------------------------------------------------
failed Test
--------------------------------------------------
freq, rf2, 0.750e9, -70.0, pm 500,    pm 1.0
    ---- lots of lines after ----

sedがテスト要約を検出した場合にのみ、テスト要約とファイル名を印刷したいと思います。

したがって、複数のファイルに対して次のような出力が必要です。

File: ./4662-0003-05132021-0953.log
Summary
--------------------------------------------------
( Cases/Passed/Failed)
Frequency Test           : (    69/     0/    69)
Carrier/Data Null Test   : (    14/     0/    14)
Total Harmonic Distortion: (     9/     9/     0)
Spur Test                : (     0/     0/     0)

File: ./4745-0001-05132021-1017.log
Summary
--------------------------------------------------
( Cases/Passed/Failed)
Frequency Test           : (    69/    68/     1)
Carrier/Data Null Test   : (    14/    14/     0)
Total Harmonic Distortion: (     9/     9/     0)
Spur Test                : (     0/     0/     0)

私は次のコマンドでこれを達成しました。

find ./ -type f -name '*.log' \
    -exec bash -c "grep -q Summary {} && echo 'File: {}' && sed -n '/Summary/,/Spur/p' {} && echo" \;

分割しgrep -q Summary ()、要約がログファイルに表示されない場合、次のエントリは実行されません。 sed -n '/Summary/,/Spur/p'「Summary」と「Spur」の間のログ部分のみが印刷されます。

-exec cmd{}と-exec cmd+の違い

\;の代わりに使う理由が気になるかもしれません+。を使用すると、+{}はコマンドラインに入力できるだけのファイル名に置き換えられます。これは私たちが望むものではなく、findはこの場合でもそれを許可しません。

人々から発見されました:

   -exec command {} +
          This variant of the -exec action runs the specified command on the selected files, but the command line is built  by
          appending  each selected file name at the end; the total number of invocations of the command will be much less than
          the number of matched files.  The command line is built in much the same way that xargs builds  its  command  lines.
          Only  one  instance  of  `{}' is allowed within the command.  The command is executed in the starting directory.  If
          find encounters an error, this can sometimes cause an immediate exit, so some pending commands may  not  be  run  at
          all.  This variant of -exec always returns true.

結論として

残念なことなので申し訳ありませんが、誰かに役立つことを願っています。

答え4

exec目的のタスクを実行する小さなスクリプトを作成し、そのスクリプトをfind。スクリプトはすでにあります。$filenameに置き換えると、$1すでにスクリプトがあるのです。あなたのスクリプトは次の形式を取ります。

#!/bin/bash
sed -i 's/$pattern/$new_pattern/' $1
echo $1 >> changelog

このスクリプトをと呼びましょうed_notify。これで、次のように選択したファイルで実行できます。

cat changelog >> changelog.old
rm changelog
find . -type f -a \( -name "*.txt" -o -name "*.git"\) -a -exec ed_notify {} \;

関連情報