ファイル名(スペースを含む)に基づいてファイルを再帰的に並べ替えて mv する

ファイル名(スペースを含む)に基づいてファイルを再帰的に並べ替えて mv する

間違えて同じディレクトリにファイルをまとめてダンプしてしまいました。幸い、ファイル名に基づいて並べ替えることができます: ''' 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg '''#ビットまたは2この特定のケースでは、いわば位置情報です。範囲は 0 ~ 9 で、ファイル名はすべて同じ長さなので、その番号は常にファイル名の同じ位置 (スペースを含む 26 番目の文字、アンダースコアで囲まれた位置) になります。このすばらしいリンクを見つけました: 名前の一部だけを検索してファイルを見つけるコマンドはありますか?

しかし、出力を move コマンドにパイプすることはできません。出力を変数にループしようとしましたが、これもうまくいかないようです。

for f in find . -name '*_0_*' ; do  mv "$f" /destination/directory ; done

このリンクに基づくと、* または引用符を間違った場所に置いてしまった可能性があります。mv: シェル スクリプトにそのようなファイルまたはディレクトリがないため、stat できません

そうは言っても、私は多くのディレクトリを持っており、それらをどこか別の場所で同一のディレクトリ構造に整理したいと考えています。

-Flowers (to be sorted)          -Flowers-buds               -Flowers-stems
     -Roses                          -Roses                      -Roses
        buds.jpg       ===>             buds.jpg     ===>           stems.jpg
        stems.jpg
        petals.jpg
     -Daisies                       -Daisies                    -Daisies
        buds.jpgs                       buds.jpg                   stems.jpg
        stems.jpg
        petals.jpg
     -Tulips                        -Tulips                     -Tulip
        buds.jpgs                       buds.jpg                  stems.jpg
        stems.jpg
        petals.jpg

...そしてその数に基づいてさらに(#)。これは bash で実行できる実用的なものですか? coreutils がインストールされたターミナルで MacOS を実行しているので、ツールは darwin (BSD) ではなく GNU Linux のように動作するはずです。

答え1

findとを使ってこれを行うこともできますmvが、ここでは最も簡単な方法ではありません。macOSを使用しているので、翻訳がプリインストールされています。Zshには、大量のファイル名を変更する便利なツールが付属しています。zmvzshターミナルで実行し、zsh で次のように実行します。

autoload zmv
zmv -n '[^_]#_([0-9])_*' '/elsewhere/${1}/${f}'

説明:

  • -nzmv、何が行われるかを表示しますが、実際には何も実行しません。表示された内容に満足したら、 なしでコマンドを再度実行します-n
  • 最初の非オプション引数はワイルドカードパターン.zmvは一致するファイルに対して動作し、一致しないファイルは無視します。
  • [^_]#は、 以外の 0 個以上の文字を意味します_。Zsh では、bash と同じワイルドカードにアクセスできます。#は、zsh のみの機能 (bash では異なる構文で使用可能) で、「前の文字の任意の数」を意味します。パターンは、[^_]#_[0-9]_*2 つのアンダースコアの間に 1 つの数字が含まれ、その数字の前に他のアンダースコアがないファイル名と一致します。
  • 括弧で囲むことで、置換テキストで[0-9]数字が使用できるようになります。$1
  • 置換テキストでは${1}、、${2}などを使用してソース パターン内の括弧で囲まれたフラグメントを参照したり、${f}完全な元の名前を参照したりできます。

たとえば、上記のスニペットは2019-02-19 20.18.58.ndpi_2_2688_2240.jpgに移動します/elsewhere/2/2019-02-19 20.18.58.ndpi_2_2688_2240.jpg。質問では、何を移動する必要があるか、どこに移動する必要があるかが正確に説明されていませんが、上記のコマンドを微調整することで、必要な結果を得ることができるはずです。わからない場合は、質問を編集して、一致する必要があるものと一致する必要があるものの具体的な例を追加してください。

サブディレクトリにファイルがある場合は、*/パターンの先頭で を使用できます。 で参照するディレクトリを一致させる必要がある場合は、を括弧で囲む必要があります。スラッシュを括弧で囲むことはできません。ディレクトリを再帰的に走査し、任意の位置でファイルを一致させたい場合は、 を使用します。${NUM}***/再帰的なグロビング例外として、(**/)置換テキスト内のディレクトリパスにアクセスするには を使用する必要があります。このような場合は、括弧を使用せずに を使用する方が簡単な場合が多くあります。${NUM}修飾子元のパスの部分を全体として抽出するには、 をオンに${f}します。たとえば、上記と同じ名前変更を行いますが、現在のディレクトリから の下の並列構造にファイルを移動しますが、ファイル名の直前に/elsewhereレベル0、などを追加します。1

autoload zmv
zmv -n '**/[^_]#_([0-9])_*' '/elsewhere/${f:h}/${1}/${f:t}'

ファイル名でファイルを一致させることが難しい場合は、変更日時で一致させるという別の方法もあります¹。しばらく変更されていないディレクトリに大量のファイルをコピーした場合、新しいファイルは最近変更されたファイルです。zshで変更日時でファイルを一致させるには、グロブ修飾子cたとえば、過去 1 時間以内に最後に変更されたファイルを一覧表示するには、次のようにします。

ls -lctr *(ch-1)

締め切り時間を探す代わりに、いいえ最も最近変更されたファイルをglob修飾子でソートするoc(ctimeの増加順にソートする)と(最初のものだけを残す)[1,N]いいえたとえば、そのディレクトリに 42 個のファイルを移動したばかりで、それ以降何も変更していないことが分かっている場合は、次のようになります。

ls -lctr *(oc[1,42])

で glob 修飾子を使用する場合はzmv、 オプションを渡す必要があります-Q。たとえば、上記のように名前に基づいてファイルをディレクトリに移動しますが、過去 1 時間以内に変更されていないファイルはすべて無視します。

zmv -n -Q '[^_]#_([0-9])_*(ch-1)' '/elsewhere/${1}/${f}'

zmvは、ファイルを上書きしないこと、衝突(同じ宛先名を持つ2つのソースファイル)がないことをチェックする安全策をいくつか持っています。ファイルの数が膨大であれば、これらの安全策には時間がかかるかもしれません。がzmv遅すぎて、名前変更の構造によって衝突が起こらないことが保証されている場合は、ループ内で行っている特定の名前変更をハードコードすることができます。zshは、bashを使用しなくても、zmvその優れた機能のおかげでbashよりも優れている傾向があります。グロビングそしてパラメータ拡張メカニズム。パフォーマンスに関しては、動的にロードするモジュールにはファイル操作用の組み込み

たとえば、名前に次の文字が含まれている場合、現在のディレクトリの下の通常のファイルをまったく新しい階層に移動するには、次のようにします_0_

zmodload -m -F zsh/files 'b:zf_*'
for x in **/*_0_*(.); do
  zf_mkdir -p /destination/directory/$x:h
  zf_mv ./$x /destination/directory/$x
done

:hこれはzshのもう一つの機能です:履歴修飾子

ファイル名の 2 つのアンダースコアの間にある最初の数字を取得し、その数字にちなんで名付けられたディレクトリにファイルを置くには、その数字を抽出する必要があります。zmv括弧で囲まれたグループの場合は自動的に実行されますが、ここでは手動で行う必要があります。

zmodload -m -F zsh/files 'b:zf_*'
for source in **/*_<->_*(.); do
  suffix=${${source:t}#*_<->_}
  n=${${source%$suffix}##*_}
  destination=${source:h}/$n/${source:t}
  zf_mkdir -p /destination/directory/$destination:h
  zf_mv ./$source /destination/directory/$destination
done

大量のファイル名を変更する他の方法について知りたい場合は、以下を参照してください。renameこのサイトでタグ付けされた質問

¹ファイルの inode 変更時刻 (「変更時刻」または略して「ctime」と呼ばれる) は、ファイルが最後に移動された時刻、またはアクセス許可などの属性が最後に変更された時刻です。これは、変更時刻 (mtime) とは異なります。

答え2

シェルは空白で入力を分割しています。**ファイル名を適切に分割するには、再帰的に bash グロブを使用できます。

shopt -s globstar
for f in **/*_0_*; do mv "$f" /dest/; done

すべてが同じ場所に行く場合、ループは必要ありません。

shopt -s globstar
mv **/*_0_*  /dest/

... または を使用すると、シェルの関与なしに、find -execfind から call に直接ファイル名を渡すことができます。exec()

find . -name '*_0_*' -exec mv {} /dest/ \;

または、readプラス記号を使用してfind -print0null で分割します。この問題には過剰ですが、グロブしてfind -execタスクを実行できない場合に役立ちます。

while IFS=  read -r -d ''; do mv "$REPLY" /dest/; done < <( find . -name '*_0_*' -print0 )

例のように、ファイル名に基づいて最上位の宛先を変更するには、${var#remove_from_start}と を使用してファイル名の一部を切り取ります。削除パターンのワイルドカードは、可能な限り少ない部分を削除します。可能な限り多く削除するには、または の${var%remove_from_end}ように操作文字を 2 倍にします。${…##…}${…%%…}

    shopt -s globstar
    for f in **/*_0_*; do            # Flowers/Roses/stems.jpg 
      file=${f##*/}                  # stems.jpg
      base=${file%.*}                # stems
      path=${f%/*}                   # Flowers/Roses
      toppath=${path%%/*}            # Flowers
      subpath=${path#*/}             # Roses
      dest=$toppath-$base/$subpath/  # Flowers-stems/Roses/
      [[ -d $dest ]] || mkdir -p "$dest"
      mv "$f" "$dest"
    done

関連情報