パターンと置換のリストを使用して、ファイルの名前を再帰的に変更します。

パターンと置換のリストを使用して、ファイルの名前を再帰的に変更します。

ファイル構造は次のとおりです。

  • ディレクトリ
    • いくつかのファイル.txt
    • 別のファイル here.log
    • さらに別のファイル.mp3
  • 別のディレクトリ
    • 他のファイル.txt
  • ルート レベルのファイル.txt
  • ルート レベルにある別のファイル.ext

私が今やりたいことは、何らかのパターン/置換ペアを含む別のファイルを入力として受け取り、それに従ってこれらのファイルを再帰的に名前変更する小さなスクリプトを実行することです。これにより、すべての「another」(大文字と小文字を区別しない) が「foo」に置き換えられ、すべての「some」が「bar」に置き換えられます。

すでに、ファイルを反復処理して入力ファイルを読み取るなど、さまざまなことを試しましたが、期待どおりに動作せず、最終的にテスト スクリプトを誤って上書きしてしまいました。ただしls、、、whileまたはsedが多数mv使用されていました。

私が自分で解決できなかった 2 つの問題は、ファイル名の空白をどのように処理するか、および以前のパターン マッチですでに名前が変更されたファイルをどのように処理しないかということでした。

たぶん、あなたは私に正しい方向を指し示してくれるでしょうか?

答え1

TOP="`pwd -P`" \
find . -type d -exec sh -c '
   for d
   do
      cd "$d" && \
         find . ! -name . -prune -type f -exec sh -c '\''
            while IFS=\; read -r pat repl
            do
               rename "s/$pat/$repl/g" "$@"
               N=$#
               for unmoved
               do
                  if [ -f "$unmoved" ]
                  then
                     set X ${1+"$@"} "$unmoved"
                     shift
                  fi
               done
               shift "$N"
               case $# in 0 ) break ;; esac
            done < patterns.csv
         '\'' x \{\} +
      cd "$TOP"
   done
' x {} +
  • findネットディレクトリのみに設定し、sh一気にダウンさせます。これにより、 の呼び出し回数が最小限に抑えられますsh
  • findこれらの各ディレクトリにregular、深度レベル 1 のみでネット ファイルを設定し、それらをsh一気に読み込みます。これにより、renameユーティリティが呼び出される回数が最小限に抑えられます。
  • whileさまざまなペアを読み込みpattern <-> replacement、すべてのファイルに適用するループを設定しますregular
  • 過程でrename-ingプロセス後もファイルが残っているかどうかは記録されますrename。ファイルがまだ存在する場合は、何らかの理由で名前を変更できなかったため、次の反復で試行されます。一方、ファイルの名前変更に成功した場合は、コマンド ライン引数リストからそのファイルを削除して、次の反復をこのファイルにpat/repl適用しません。pat/repl

答え2

rPairs="/tmp/rename_pairs" \
find . -type f -exec sh -c '
   while read -r old new; do
      rename "s/$old/$new/i" "$@"
   done < "$rPairs"
' x {} +

名前変更ペア ファイルに非 ASCII 文字が含まれておらず、このファイルが検索パスから離れた場所に配置されていると仮定します。

答え3

Rakesh Sharma の回答の後、もう少し実験して少し眠った後、正しい方向に進みました。

最終的に、次のスクリプトを思いつきました。

#!/bin/bash


while IFS=";" read pattern replacement
do
  if [[ ! -z $pattern ]]
  then
    echo "Checking files for pattern '$pattern'."

    find ./files -name "*$pattern*" -type f | while read fpath
    do
      fname=$(basename "$fpath")
      dname=$(dirname "$fpath")

      echo "  Found file '$fname' in directory '$dname'. Renaming to '${fname/$pattern/$replacement}'."
      mv -- "$fpath" "$dname/${fname/$pattern/$replacement}"
    done
  fi
done < patterns.csv

ファイルを読み取りpattern.csv、その行をループして、変数$pattern$replacement変数を埋めます。2 番目のステップでは、./files現在のパターンに一致するディレクトリ内のすべてのファイルが検索されます。2 番目のパターンが一致したときに、再度ファイル名を変更しようとすると失敗するため、これを実行する必要が生じます。最後に、シェル パラメータ置換を使用して、ファイル自体の名前のみを変更し、ファイルを含むディレクトリの名前は変更しません。

うまくいかないのは、大文字と小文字を区別せずに一致するものを置き換えることですが、私はそれで生きていけると思います。

答え4

覚えておくべき重要な点は、ディレクトリ ツリーのトラバースは時間のかかるプロセスであるため、1 回だけ実行されるということです。まず、findツリー内のディレクトリのみを調べます。次に、ディレクトリごとに、regular filesその下にあるすべてのディレクトリを検索します (ここでは再帰はありません)。次に、これらのファイル名に名前変更変換を適用し、同時に成功したかどうかを記録します。成功した場合は、while ループから抜け出し、このファイルに次の patt/repl が適用されないようにします。

tempd="`mktemp -d`" \
find . -type d -exec sh -c '
   cd "$1" && \
   for f in ./*
   do
      [ -f "$f" ] || continue
      while IFS=\; read -r patt repl
      do
         case $f in
            ./*"$patt"* )
               rename -v "s/$patt/$repl/g" "$f" 2>&1 | tee "$tempd/$f"
               case $(< "$tempf/$f") in "$f renamed "* ) break ;; esac ;;
         esac
      done < /tmp/patterns.csv
   done
' {} {} \;

関連情報