競合なしでファイル名番号を移動するには?

競合なしでファイル名番号を移動するには?

次のファイルのリストを検討してください。

$ touch index-{1,2,3,4,5,6,7,8,9}.txt

変えたいなら下にだから彼らは最初から始まります。比較的簡単:

$ rename --verbose 's/^index-([1-9])\.txt$/$1/; $_="index-" . ($_ - 1) . ".txt"' index-*.txt

man bashこれは、指定されたglobがアルファベット順にソートされ、次のように名前が変更されるために機能しますindex-1.txtindex-0.txt 今後index-2.txtに名前が変更されましたindex-1.txt

変更しようとすると失敗します。戻る、または番号が次の場合異なる長さ:

$ touch index-{10,11}.txt
$ rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' index-*.txt
index-10.txt not renamed: index-11.txt already exists
index-11.txt renamed as index-12.txt
index-1.txt not renamed: index-2.txt already exists

可能な長期修正:

  • rename競合するオプションが発生しなくなるまで作業順序を再指定してください。
  • renameまず、ファイルを一時ディレクトリに移動してから新しい名前に戻すオプションです。
  • rename最初のステップでは、元の名前や新しい名前と競合しない一意の名前を使用して2つのステップにファイル名を変更するオプションです。
  • 自然なアライメントをする方法+裏返すBash グローバル変数。

この問題を解決する簡単な方法はありますか?

答え1

一連の名前変更がどの順序で行われるべきかを決定するための一般的な解決策を見つけるのに役立つかもしれないUnixには、よく知られていない標準コマンドがあります。tsort

次のファイルに実行する名前変更のリストがあるとしますrenames.txt(実演のために、その名前にスペースが含まれていないとします)。

d a
e f
b e
a c

d名前が変更されるため、まず名前を変更する必要があるaという意味です。したがって、ファイル名を変更する必要がある順序とは逆の部分ソート順序があります。ad

tsortソート順の部分リストからソート順全体を推測するツールです。ループがあるとエラーが返されるため、解決策がない状況を検出するのに役立ちます。その入力を適用すると、tsort次のようになります。

b
d
e
a
f
c

後に名前を変更するb必要があることがわかります。私達は利用できますde牛に似た一種の栄養 tac(一部のシステムでも利用可能tail -r)順序を逆にするには:

c
f
a
e
d
b

そして、名前変更リストに追加してください。

tsort renames.txt | tac | awk '
  NR==FNR {
    ren[$1] = $2
    next
  }
  $1 in ren {
    print "mv -i --", $1, ren[$1]
  }' renames.txt -

これは私たちに以下を提供します。

mv -i -- a c
mv -i -- e f
mv -i -- d a
mv -i -- b e

その後、パイプラインを介して実行できますsh

ただし、上記のコードはループを検出するために上記の終了状態を確認しておらず、tsortファイル名に特殊なシェル文字を含めないでください。

これ堅牢性読者のための練習として残しました;-)

答え2

より堅牢性のために書き直された@l0b0の解決策:

printf '%s\0' index-*.txt |
  sort --zero-terminated --field-separator - --key 2rn |
  xargs -0r rename --verbose '
    s/^index-([0-9]+)\.txt$/$1/;
    $_="index-" . ($_ + 1) . ".txt"'

解決策を自由に追加していただければ回答を削除します。

@l0boのソリューションはUnixではなくGNU専用です(GNUはUnixではありません)。

答え3

これは上記の特定のケースに適用されます。

rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' $(printf '%s\n' index-*.txt | sort --field-separator - --key 2n | tac)

しかし:

  1. それは処理しませんスペースファイル名に
  2. 任意のファイル名の場合、状況ははるかに複雑です。

答え4

コンテキストを説明すると、タイムラプス用のLapseIt Proアプリを使用していましたが、何らかの理由で停止してタイムラプスを再起動しましたが、次のような別のプロジェクト名が指定されました。

Proj10_img0000000x

3000個のファイル名をすべて古いプロジェクトProj9_img0000000xに変更し、最後に番号を変更してプロジェクトに連続した名前+番号フィードを持たせると、すべての画像を1つのプロジェクトビデオにレンダリングできます。

Proj10_img00000001 --> Proj10_img00003249をProj9_img00004325 --> Proj9_img00007574に変更する必要があります。

要約すると、最終結果はプロジェクトフォルダProj9_img00000001 --> Proj9_img00007574で次のようになります。

Mac OSXを使用している場合(私はMojaveを使用しています)

  1. Finder で変更するすべてのファイルを強調表示します。
  2. 強調表示されたファイルの部分を右クリックします。
  3. 「X数量」アイテムの名前を変更を選択します。
  4. 「Finderアイテムの名前変更」ポップアップが開きます。
  5. このポップアップを使用すると、他の便利なオプションと簡単に名前を変更して番号を変更できます。

それでもコマンドラインgrep / sed / awk / cut&findコマンドを見ていますが、今は上記がうまくいきます(公開するにはできるだけ早くビデオを完了する必要があります)。

関連情報