ファイル名に基づいてファイルを複数のディレクトリにソートしますか?

ファイル名に基づいてファイルを複数のディレクトリにソートしますか?

ディレクトリに1000個のファイルがあり、ファイル名に基づいてサブディレクトリにソートしたいと思います。これらはすべて集合構造p-[番号]_n-[番号]_a-[番号].[ext]に沿って一貫して名前が付けられます。

これは小さなサンプルです...

  • p-12345_n-987_a-1254.jpg
  • p-12345_n-987_a-9856.pdf
  • p-12345_n-987_a-926.docx
  • p-12345_n-384_a-583.pdf
  • p-12345_n-384_a-987.pdf
  • p-2089_n-2983_a-2348.gif
  • p-2089_n-1982_a-403.jpeg
  • p-38422_n-2311_a-126.pdf
  • p-38422_n-2311_a-5231.docx

私が望むのは、次のフォルダ構造です。

p-12345
  ⊢ n-987
    ⊢ p-12345_n-987_a-1254.jpg
    ⊢ p-12345_n-987_a-9856.pdf
    ⊢ p-12345_n-987_a-926.docx
  ⊢ n-384
    ⊢ p-12345_n-384_a-583.pdf
    ⊢ p-12345_n-384_a-987.pdf
p-2089
  ⊢ n-2983
    ⊢ p-2089_n-2983_a-2348.gif
  ⊢ n-1982
    ⊢ p-2089_n-1982_a-403.jpeg
p-38422
  ⊢ n-2311
    ⊢ p-38422_n-2311_a-126.pdf
    ⊢ p-38422_n-2311_a-5231.docx

これが意味があることを願っています。

このようにファイルを構成するスクリプトを作成できますか?

編集:明確にするために:はい、私の質問は次のようになります。どのようにファイルをクリーンアップするスクリプトを書くことはできますか? :) 私はUnixとコマンドラインに初めて触れました。これまでは、デフォルトのシェルスクリプトのみを作成/使用していました。答えには正規表現を含めることができるようですが、それ以外はどこから始めるべきかわかりません。

私が思いついた最高のアイデアは

  1. ファイルリストをテキストファイルにエクスポート
  2. 「_n」と「_a」を検索し、「/n」と「/a」に置き換えます。
  3. 次に、一連のmvコマンドを作成します。
  4. シェルスクリプトとして保存

私はこれが必要なものよりはるかに冗長であると確信しています。また、後でより多くのファイルを使用してこれを行う必要がある場合に備えて、再現可能なものが欲しいと思います。

答え1

確かに:

#!/bin/bash
for i in p-*_n-*.*; do
        Ppart=${i/_n-*}
        x=${i/${Ppart}_/}
        nPart=${x/_a-*}
        mkdir -p $Ppart/$nPart
        mv $i $Ppart/$nPart
done

指定したパターンに一致するすべてのファイル名を繰り返し始めます。各ループのシェル置換を使用して、_n-P部分(最初のレベルディレクトリ)を提供する部分で始まるファイル名の最後の部分を削除します。今、部品n-から始めてN個の部品が必要です_a-。これは2段階で行われる。まず、Ppartを削除してから、_a-その部分から始まる最後の部分を削除します。

これでmkdir -p、必要なディレクトリを作成するために使用されます。mkdir -pパスがすでに存在する場合、エラーは発生しないため、mkdir -pコマンドの実行を決定する前にディレクトリが存在するかどうかをテストするのではなく、単に実行する方が簡単です。

最後に、ファイルを正しいディレクトリに移動します。

答え2

すでに指摘したように、短い答えは「はい」です。

長い答えは次のとおりです。awkディレクトリ構造の基礎となるファイル名要素を抽出するbashスクリプトを使用してこれを実行できます。次のようになります(「1行」の簡潔さよりも読みやすさが強調されている場合)。

#!/bin/bash


for FILE in p-*
do
    if [[ ! -f $FILE ]]; then continue; fi

    LVL1="$(awk '{match($1,"^p-([[:digit:]]+)_[[:print:]]*",fields); print fields[1]}' <<< $FILE)"
    LVL2="$(awk '{match($1,"^p-([[:digit:]]+)_n-([[:digit:]]+)_[[:print:]]*",fields); print fields[2]}' <<< $FILE)"

    echo "move $FILE to p-$LVL1/n-$LVL2"
    if [[ ! -d "p-$LVL1" ]]
    then
    mkdir "p-$LVL1"
    fi

    if [[ ! -d "p-$LVL1/n-$LVL2" ]]
    then
    mkdir "p-$LVL1/n-$LVL2"
    fi

    mv $FILE "p-$LVL1/n-$LVL2"
done

説明する:

  • 現在のディレクトリで「p-」で始まるすべてのファイルに対してループを実行します。
  • ループの最初のコマンドは、ファイルが存在することを確認し、空のディレクトリの回避策です(このフォーラムでは常に次のように説明するため必要です)。出力を解析しません。lsしたがって、そのようなものはFILES=$(ls p-*); for FILE in $FILES; do ...禁止されていると見なされます)。
  • 次に、(疑いのように正規表現を使用して)p-ディレクトリ構造の最初のレベルを生成するために必要なものとの間の数を抽出し、2番目のレベルの間の数にも等しく適用されます。アイデアは、入力で指定された正規表現が発生する場所を見つけるだけでなく、「フィールド」配列内の括弧内のすべての要素に「完全な」値を提供する関数を使用することです。_nawkn-_amatch( ... )
  • 第3に、所望のディレクトリ構造の第1および第2レベルのディレクトリが既に存在することを確認する。それ以外の場合は生成します。
  • 最後に、ファイルをターゲットディレクトリに移動します。

詳細については、以下を確認してください。高度なbashスクリプトガイドそしてGNU Awkユーザーガイド

スクリプトと正規表現をより多く使用すると、より簡潔にすることができます。たとえば、上記のスクリプトでは、ディレクトリ/サブディレクトリパスの生成を単一の呼び出しawkに簡単に減らすことができます。

  • まず、ディレクトリ名は次のとおりです。実際に p-<number>また、n-<number>ファイル名と同様に、awk次のように入力してこれらの文字を抽出することもできます。 match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields)

  • awk適切なパラメータを使用してディレクトリサブディレクトリパスを作成することで、ワークロードをさらに削減できますprint

awk '{match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields); print fields[1] "/" fields[2]}'

p-12345/n-384ファイル(例:)用に簡単に作成されますp-12345_n-384_a-583.pdf。これを@wurtelが指定した使い方と組み合わせると、mkdir -pスクリプトは次のようになります。

for FILE in p-*
do
    if [[ ! -f $FILE ]]; then continue; fi

    TARGET="$(awk '{match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields); print fields[1] "/" fields[2]}' <<< $FILE)"
    echo "move $FILE to $TARGET"

    mkdir -p "$TARGET"
    mv $FILE $TARGET
done

答え3

Python(3) の他のバージョン:

import os

sourcepath='/path/to/source'
destination='/path/to/destination'

(_,_,fnames) = next(os.walk(sourcepath))
for f in fnames:
    subpath = '/'.join(f.split('_')[:-1])
    print("Moving {} to {}".format(os.path.join(sourcepath, f), os.path.join(destination, subpath , f)))
    os.makedirs(os.path.join(destination, subpath), exist_ok=True)
    os.rename(os.path.join(sourcepath, f), os.path.join(destination, subpath , f))

答え4

素敵な一行はどうですか?

ls | awk -F"_" '{system("mkdir -p " $1 "/" $2 "&& mv " $0 " " $1 "/" $2 "/" $0)}'

_作成したいディレクトリに応じてファイル名部分を分割し、変更されていないファイル名を新しく作成したディレクトリに移動します。

関連情報