findとsedを使用して再帰的にファイルの名前を変更します

bash find replace scripting sed
findとsedを使用して再帰的にファイルの名前を変更します

たくさんのディレクトリを調べて、_test.rbで終わるすべてのファイルの名前を_spec.rbで終わるように変更します。 それは私がbashをどのように扱うかをまったく理解していなかったので、今回はそれを釘付けにするために努力を払うと思った。 私はこれまでのところ短いと思いますが、私の最善の努力は:

find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;

NB:execの後に余分なエコーがあるため、テスト中にコマンドが実行される代わりに出力されます。

実行すると、一致した各ファイル名の出力は次のようになります。

mv original original

i.e. sedによる代替は失われました。 トリックは何ですか?

  74  40


ベストアンサー

元の問題に最も近い方法でそれを解決するには、おそらくxargsの「コマンドラインごとの引数」オプションを使用します。

find . -name *_test.rb | sed -e "p;s/test/spec/" | xargs -n2 mv

現在の作業ディレクトリ内のファイルを再帰的に検索し、元のファイル名( p)に続いて変更された名前(` s / test / spec / )をエコーし​​、すべてをペアで mv`に送ります( xargs- n2)。 この場合、パス自体に文字列「test」が含まれていないことに注意してください。

109


これは、「sed」が文字列「{}」を入力として受け取るために発生します。

find . -exec echo `echo "{}" | sed 's/./foo/g'` \;

ディレクトリ内の各ファイルに対して再帰的に `foofoo`を出力します。 この動作の理由は、コマンド全体を展開するときにパイプラインがシェルによって1回実行されるためです。

「find」はシェルを介してコマンドを実行せず、パイプラインやバッククォートの概念がないため、「find」がすべてのファイルに対して実行するような方法で「sed」パイプラインを引用する方法はありません。 GNU findutilsマニュアルでは、パイプラインを別のシェルスクリプトに入れることにより、同様のタスクを実行する方法を説明しています。

#!/bin/sh
echo "$1" | sed 's/_test.rb$/_spec.rb/'

( `sh -c`と大量の引用符を使用して、これらすべてを1つのコマンドで実行する方法がありますが、試しません。)

31


あなたは次のような他の方法を検討したいかもしれません

for file in $(find . -name "*_test.rb")
do
  echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/`
done

22


これはもっと短い

find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;

17


必要に応じて、sedを使用せずに実行できます。

for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done

$ {var %% suffix}`は `var`の値から suffix`を取り除きます。

または、sedを使用してそれを行うには:

for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done

9


シェルとして「bash」を使用していると言いますが、この場合、バッチの名前を変更するために実際に「find」と「sed」は必要ありません…​

シェルとして「bash」を使用していると仮定します。

$ echo $SHELL
/bin/bash
$ _
  1. そして、いわゆる「globstar」シェルオプションを有効にしたと仮定します:

$ shopt -p globstar
shopt -s globstar
$ _
  1. そして最後に、 `rename`ユーティリティをインストールしたと仮定します(見つかりました
    `util-linux-ng`パッケージ内)

$ which rename
/usr/bin/rename
$ _
  1. 次に、* bash one-liner *でバッチ名を変更できます
    次のとおりです。

$ rename _test _spec **/*_test.rb

globstar`シェルオプションは、ディレクトリ階層にどれだけ深くネストされていても、bashが一致するすべての * _test.rb`ファイルを検出するようにします…​ オプションの設定方法については、「help shopt」を使用してください)

9


最も簡単な方法

find . -name "*_test.rb" | xargs rename s/_test/_spec/

最速の方法(プロセッサが4つあると仮定):

find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/

処理するファイルの数が多い場合、xargsにパイプされたファイル名のリストにより、結果のコマンドラインが許可される最大長を超える可能性があります。

`getconf ARG_MAX`を使用してシステムの制限を確認できます

ほとんどのLinuxシステムでは、「free -b」または「cat / proc / meminfo」を使用して、使用するRAMの量を確認できます。それ以外の場合は、「top」またはシステムアクティビティモニターアプリを使用します。

より安全な方法(使用するRAMが1000000バイトあると仮定):

find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/

5


このため、 sed`は必要ありません。 process substitutionを介して `find`の結果が渡される while`ループで完全に独り立ちできます。

したがって、必要なファイルを選択する「find」式がある場合は、次の構文を使用します。

while IFS= read -r file; do
     echo "mv $file ${file%_test.rb}_spec.rb"  # remove "echo" when OK!
done < <(find -name "*_test.rb")

これにより、ファイルが「検索」され、すべての名前が変更され、末尾から文字列「test.rb」が削除され、「 spec.rb」が追加されます。

このステップでは、https://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion [Shell Parameter Expansion]を使用します。ここで、「$ {var%string}」は最短一致パターンを削除します ” 「$ var」からの文字列」。

$ file="HELLOa_test.rbBYE_test.rb"
$ echo "${file%_test.rb}"          # remove _test.rb from the end
HELLOa_test.rbBYE
$ echo "${file%_test.rb}_spec.rb"  # remove _test.rb and append _spec.rb
HELLOa_test.rbBYE_spec.rb

” ” ‘

例を参照してください。

$ tree
.
├── ab_testArb
├── a_test.rb
├── a_test.rb_test.rb
├── b_test.rb
├── c_test.hello
├── c_test.rb
└── mydir
    └── d_test.rb

$ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb")
mv ./b_test.rb ./b_spec.rb
mv ./mydir/d_test.rb ./mydir/d_spec.rb
mv ./a_test.rb ./a_spec.rb
mv ./c_test.rb ./c_spec.rb

2


私が好きなramtamの答えでは、検索部分は正常に機能しますが、パスにスペースがある場合は残りは機能しません。 私はsedにあまり詳しくありませんが、その答えを次のように修正することができました。

find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv

私の使用例では、最終コマンドは次のように見えるため、このような変更が本当に必要でした

find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv

1


やり直すつもりはありませんが、https://stackoverflow.com/questions/5587942/commandline-find-sed-exec [Commandline Find Sed Exec]への回答として書きました。 そこで、質問者は、ツリー全体を移動する方法を知りたくて、ディレクトリを1つまたは2つ除外し、文字列_ “OLD” を含むすべてのファイルとディレクトリの名前を “NEW” _に変更しました。

以下の_how_を骨の折れる冗長性*で説明することに加えて、このメソッドは組み込みのデバッグが組み込まれているという点でもユニークです。 コンパイルされ、要求された作業を実行するために実行する必要があると思われるすべてのコマンドを変数に保存することを除いて、基本的には何も書かれていません。

また、可能な限り明示的に*ループを回避*します。 「パターン」の複数の一致に対する「sed」再帰検索に加えて、私が知る限り、他の再帰はありません。

最後に、これは完全に「null」で区切られています-「null」以外のファイル名の文字にはつまずきません。 私はあなたがそれを持つべきではないと思います。

ところで、これは_REALLY_高速です。 見て:

% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED < :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo < Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh < $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" |  tail -n 2 )


    0.06s user 0.03s system 106% cpu 0.090 total



    Lines  Words  Bytes
    115     362   20691 -



    mv .config/replacement_word-chrome-beta/Default/.../googlestars \
    .config/replacement_word-chrome-beta/Default/.../replacement_wordstars

*注:*上記の「関数」では、「find printf」と「sed -z -e」および「:; recursive regex test; t」を適切に処理するために、「sed」と「find」の「GNU」バージョンが必要になる可能性があります呼び出します。 これらが利用できない場合、いくつかの小さな調整で機能を複製することができます。

これにより、最初から最後まで必要なすべての操作をごくわずかに行うことができます。 私は sed`で fork`をしましたが、いくつかの `sed`再帰的分岐テクニックも練習していたので、ここに来ました。 理髪師の学校で割引ヘアカットをするようなものです。 これがワークフローです。

  • rm -rf $ {UNNECESSARY}

  • 削除する可能性のある関数呼び出しを意図的に除外しました
    あらゆる種類のデータを破壊します。 あなたは、 。/ app`は望ましくないかもしれないと言います。 それを削除するか、事前に別の場所に移動するか、あるいは、プログラムで実行するために `find`を実行するための \(-path PATTERN -exec rm -rf \ {\} \) `ルーチンを作成することもできますが、それはすべてあなたのものです。

  • _mvnfind" $ {@} "

  • 引数を宣言し、ワーカー関数を呼び出します。 `$ {sh_io}`は
    関数からの戻り値を保存するという点で特に重要です。 `$ {sed_sep}`はすぐに来ます。これは、関数内の `sed`の再帰を参照するために使用される任意の文字列です。 `$ {sed_sep}`が、実行されたパス名またはファイル名のいずれかで潜在的に見つかる可能性のある値に設定されている場合…​ まあ、ただそれをさせないでください。

  • mv -n $ 1 $ 2

  • ツリー全体が最初から移動されます。 それは多くを節約します
    頭痛;私を信じてください。 あなたがしたいことの残り-名前の変更-は、単にファイルシステムのメタデータの問題です。 たとえば、あるドライブから別のドライブに、またはあらゆる種類のファイルシステムの境界を越えてこれを移動する場合は、1つのコマンドで一度に移動した方が良いでしょう。 また、安全です。 mv`に設定された -noclobber`オプションに注意してください。書かれているように、この関数は `$ {TGT_DIR}`が既に存在する場所に `$ {SRC_DIR}`を配置しません。

  • read -R SED <

  • 私はここでsedのすべてのコマンドを見つけて、手間を省き、
    それらを変数に読み込み、以下のsedにフィードします。 以下の説明。

  • 見つけます。 -name $ {OLD} -printf

  • 「find」プロセスを開始します。 find`を使用すると、何でも検索できます
    関数の最初のコマンドで場所から場所へのすべての `mv`操作をすでに行っているため、名前の変更が必要です。 たとえば、 `exec`呼び出しのように、
    find`で直接アクションをとるのではなく、代わりに `-printf`を使用してコマンドラインを動的に構築します。

  • %dir-depth:tab: 'mv'%path-to-$ {SRC} '' $ {sed_sep} '%path-again:null delimiter:'

  • 「find」がファイルを見つけた後、必要なファイルを直接ビルドして印刷します
    名前変更の処理に必要なコマンドのうち(most)。 各行の先頭に付けられた `%dir-depth`は、ツリー内のファイルまたはディレクトリの名前を、まだ名前が変更されていない親オブジェクトに変更しようとしていないことを確認するのに役立ちます。 `find`はあらゆる種類の最適化手法を使用してファイルシステムツリーをたどりますが、必要なデータを安全に操作できる順序で返すかどうかはわかりません。 これが次の理由です…​

  • ソート-一般数値-ゼロ区切り

  • すべての find`の出力を%directory-depth`に基づいてソートし、
    $ \ {SRC}との関係で最も近いパスが最初に機能します。 これにより、存在しない場所へのファイルの `mv`ingに関連するエラーを回避し、再帰ループの必要性を最小限に抑えます。 (実際には、ループを見つけるのが難しい場合があります

  • sed -ex:rcrs; srch |(save $ {sep} * til)$ {OLD} | \ saved $ {SUBSTNEW} |; til $ {OLD = 0}

  • これはスクリプト全体で唯一のループであり、ループするだけだと思います
    置換が必要になる可能性のある複数の$ \ {OLD}値が含まれる場合に備えて、各文字列に対して2番目の `%Path`が出力されます。 私が想像した他のすべてのソリューションは、2番目の「sed」プロセスを必要とし、短いループは望ましくないかもしれませんが、確かに、プロセス全体の生成と分岐を打ち負かしています。

  • 基本的に、 `sed`がここで行うのは$ \ {sed_sep}の検索です。
    それを見つけたら、$ \ {OLD}が見つかるまでそれと見つかったすべての文字を保存し、$ \ {NEW}に置き換えます。 その後、$ \ {sed_sep}に戻り、文字列内で複数回出現する場合は、再び$ \ {OLD}を探します。 見つからない場合は、変更された文字列を「stdout」に出力し(次に次にキャッチします)、ループを終了します。

  • これにより、文字列全体を解析する必要がなくなり、
    もちろん$ \ {OLD}を含む必要がある mv`コマンド文字列の前半はそれを含み、後半は$ \ {OLD}の名前を mvから消去するのに必要な回数だけ変更されます`の宛先パス。

  • sed -ex …​- ex search |%dir_depth(save *)$ {sed_sep} |(only_saved)| out

  • ここでの2つの -exec`呼び出しは、2番目の fork`なしで発生します。 の中に
    最初に、見てきたように、必要に応じて find`の -printf`関数コマンドによって提供される `mv`コマンドを変更して、$ \ {OLD}のすべての参照を$ \ {NEW}に適切に変更しますが、そのためには、最終出力に含まれない任意の参照ポイントを使用する必要がありました。 したがって、 `sed`が必要なことをすべて完了したら、それを渡す前にホールドバッファから参照ポイントを消去するように指示します。

  • そして今すぐ戻ってきました *

`read`は次のようなコマンドを受け取ります。

% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000

関数の外で自由に調べることができる「$ {sh_io}」として「$ {msg}」に「読み込み」ます。

クール。

-Mike

1


タイトルとURLをコピーしました