ファイル名でアポストロフィの名前を繰り返し変更します'(Bash)

ファイル名でアポストロフィの名前を繰り返し変更します'(Bash)

ファイルからアポストロフィを削除する必要があります。私はいくつかのアプローチを試しました。スタック交換

私はSynology NASを使用しているので、PythonやPerlがなく、特定のディレクトリを除外する必要があります(find -prune)。

次の find コマンドはテスト中に通常動作するようです(エコーを使用)。

find . -depth \( -type d -name "@*" -o -name "*#recycle*" -o -name "*SynoResource*" -prune \) -o \( -name "*\'*" -execdir bash -c 'for f; do echo mv ${f/\\\'/\\\\'} `echo $f | sed 's/\\\'/X/g'`; done' _ {} + \)

「911's.zip」などのファイルの場合、アポストロフィ 'をXに置き換えると次のようになります(上記のコマンドを使用)。

mv ./911's.zip ./911Xs.zip

コマンドを実行するために「echo」を削除すると、次のエラーが発生します。

mv: target '911Xs.zip' is not a directory

対話型シェルでは、次のようにファイル名を変更できます。

mv 911\'s.zip 911Xs.zip

もちろん'(のような\'sが複数ある場合でも${f/\'/X})脱出しようとしましたが、うまくいきませんでした。

どんなアイデアがありますか?

ありがとう、

ゲイリー

ベストアンサー1

いくつかの問題:

  • プロセスが最初に残るため、-prune結合できないため、ディレクトリの内容全体が処理された後は遅すぎるため、何の効果もありません。-depth-depth-prune
  • あなたの-prune場所が間違っています。どこに置くと、一致するファイルにのみ適用されます-name "*SynoResource*"。同じです。-type dにのみ適用されます-name "@*"
  • パラメータ拡張とコマンド置換に二重引用符がないため、分割+グローブが発生します。
  • echo通常、任意のデータの出力には使用できません。
  • '内部に一重引用符文字列を含めることはできません。'リテラル値をコマンドに渡す必要がある場合は、別のシェル引用演算子("..."またはバックスラッシュ)を使用して一重引用符の外に引用する必要があります。
  • 'それは彼らにとって特別なものではなくmv、それを避ける必要もありません。これはシェルに特有で強力な参照演算子です。sedfind
  • $(...)廃止予定の代わりに使用する必要があります`...`(バックスラッシュが含まれる場合はより複雑になります)。

だからここにあります:

LC_ALL=C Q="'" find . -depth \
  ! -path '*/@*' \
  ! -path '*#recycle*' \
  ! -path '*SynoResource*' \
  -name "*'*" -execdir bash -c '
    for file do
      mv -i -- "$file" "${file//$Q/X}"
    done' bash {} +

ここ-pruneでは動作しないため(GNUでも呼び出されます)-depthを使用して、フルパスに基づいて除外されたディレクトリ内のファイルをフィルタリングします。ただし、これはパフォーマンスの観点から理想的ではないディレクトリに移動することを意味します。-path-wholenamefindfind

を使用することは、デフォルトの名前を変更するだけでよいことを意味しますが、sを含むファイルを含むすべてのディレクトリで少なくとも1つを実行-execdirすることになります。または、次のことができます。bash'

LC_ALL=C Q="'" find . -depth \
  ! -path '*/@*' \
  ! -path '*#recycle*' \
  ! -path '*SynoResource*' \
  -name "*'*" -exec bash -c '
    for file do
      dir=${file%/*} base=${file##*/}
      mv -i -- "$file" "$dir/${base//$Q/X}"
    done' bash {} +

これにより、スクリプトの実行中に誰かがファイルとディレクトリの名前を変更するのに対してより効率的ですが、安全性が低くなります。

練習実行の場合は、mvコマンドを次のように置き換えることができます。

(PS4="Would run"; set -x; : mv -- "$file" "$dir/${base//$Q/X}")

echobashこれは、トレース出力であいまいにならないようにするために必要な場所に引用符を使用するため、asを使用するよりも優れています。トレース出力は、同じコマンドを実行するために使用できる有効なシェルコードのように見えます。

ここで-exec置き換えを使用すると、-execdir名前が変更されたファイルを明確に追跡することもできます。

ディレクトリの深さを最初に使用しますが、処理を続行するには、出力で-pruneGNU(および利用可能な場合はGNU)を使用してそれを反転する別の方法があります。tac -s ''xargsfind -print0

LC_ALL=C find . -depth \
  '(' -name '@*' -o \
      -name '*#recycle*' -o \
      -name '*SynoResource*' \
  ')' -type d -prune -o \
  -name "*'*" -print0 |
  tac -s '' |
  Q="'" xargs -r0 bash -c '
    for file do
      dir=${file%/*} base=${file##*/}
      mv -i -- "$file" "$dir/${base//$Q/X}"
    done' bash {} +

GNUはありませんが、bashがtac4.4xargs以上の場合は、次のこともできます。

LC_ALL=C find . -depth \
  '(' -name '@*' -o \
      -name '*#recycle*' -o \
      -name '*SynoResource*' \
  ')' -type d -prune -o \
  -name "*'*" -print0 |
  Q="'" bash -c '
    readarray -td "" files &&
      for (( i = ${#files[@]} - 1; i >= 0; i-- )); do
        file=${files[i]}
        dir=${file%/*} base=${file##*/}
        mv -i -- "$file" "$dir/${base//$Q/X}"
      done' bash {} +

(まだテストしたことはありませんので参考にしてください。)

おすすめ記事