Umbenennen einer großen Anzahl von Bilddateien mit Bash

16

Ich muss ca. umbenennen. 70.000 Dateien. Zum Beispiel: Von sb_606_HBO_DPM_0089000bis sb_606_dpm_0089000usw.

Der Nummernkreis reicht von 0089000bis 0163022. Es ist nur der erste Teil des Namens, der geändert werden muss. Alle Dateien befinden sich in einem einzigen Verzeichnis und sind fortlaufend nummeriert (eine Bildsequenz). Die Nummern müssen unverändert bleiben.

Wenn ich das in der Bash versuche, wird mir klar, dass die Argumentliste zu lang ist.

Bearbeiten:

Ich habe zuerst versucht, eine einzelne Datei umzubenennen mit mv:

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Dann habe ich versucht, einen Bereich umzubenennen (ich habe letzte Woche hier gelernt, wie man eine Menge Dateien verschiebt, also dachte ich, dass die gleiche Syntax für das Umbenennen der Dateien funktionieren könnte ...). Ich glaube, ich habe Folgendes versucht (oder so ähnlich):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
Reich
quelle
4
Für Rezensenten : Ich denke nicht, dass dies ein Duplikat ist. Die meisten CLI-Antworten auf die andere Frage funktionieren hier nicht, da eine große Anzahl von Dateien mit dem Shell- ARG_MAXLimit kollidiert . Da diese Frage ausdrücklich nach einer Befehlszeilenlösung fragt, stimmen (möglicherweise gleiche) GUI-Lösungen wie in der anderen Frage ebenfalls nicht überein.
Dessert
1
Ich halte das nicht für eine Trickserei, da es in Ordnung ist, mehr als eine Frage zum Umbenennen von Dateien zu haben. Bitte lassen Sie uns keine spezifischen Fragen zu generischen Ressourcen schließen, die diese nicht beantworten ...
Zanna
1
@rich Wenn Sie explizit bearbeiten können, welchen Befehl Sie ausprobiert haben, ist es klarer, dass dies kein Betrug ist. (Dies zeigt uns, dass Sie sich dieser Vorgehensweise bewusst sind.) Prost.
Sparhawk
2
Reich, deine Frage ist kein Schwachsinn, weil es eine bestimmte Frage ist. Mach dir darüber keine Sorgen. Noch wichtiger ist, dass das Bearbeiten einer Frage, nachdem sie eine Reihe von Antworten erhalten hat, wahrscheinlich keine gute Idee ist, da Ihre Änderungen die Gültigkeit der vorhandenen Antworten beeinträchtigen können. Jetzt habe ich das Gefühl, dass meine Antwort erklären sollte, warum mv {1..2} {3..4}es nicht funktioniert, was ein ganz anderes Problem ist als ARG_MAX... Alle anderen, die geantwortet haben, werden wahrscheinlich dasselbe fühlen! Aus meiner Sicht möchte ich, dass Sie Ihre letzte Änderung rückgängig machen und, wenn Sie möchten, eine ganz neue Frage zum mvUmgang mit Bereichen stellen
Zanna,
1
@Sparhawk das OP schrieb ganz klar, von der ersten Version der Frage, dass das Problem der argument list too longFehler ist. Es besteht kein Grund zur weiteren Klärung, dies ist eindeutig kein Betrug, da wir eine Problemumgehung für den Umgang mit ARG_MAX benötigen und die Antworten im vorgeschlagenen Duplikat dies nicht tun.
Terdon

Antworten:

25

Eine Möglichkeit ist die Verwendung findmit -execund der +Option. Dadurch wird eine Argumentliste erstellt, die Liste wird jedoch in so viele Aufrufe unterteilt, wie erforderlich sind, um alle Dateien zu verarbeiten, ohne die maximale Argumentliste zu überschreiten. Es ist geeignet, wenn alle Argumente gleich behandelt werden. Dies ist der Fall bei rename, aber nicht bei mv.

Möglicherweise müssen Sie Perl umbenennen:

sudo apt install rename

Dann können Sie zum Beispiel verwenden:

find . -maxdepth 1 -exec rename -n 's/_HBO_DPM_/_dpm_/' {} +

Entfernen Sie -nnach dem Testen, um die Dateien tatsächlich umzubenennen.

Zanna
quelle
11

Ich werde drei Alternativen vorschlagen. Jedes ist ein einfacher einzeiliger Befehl, aber ich werde Varianten für kompliziertere Fälle bereitstellen, hauptsächlich für den Fall, dass die zu verarbeitenden Dateien mit anderen Dateien im selben Verzeichnis gemischt werden.

mmv

Ich würde den Befehl mmv aus dem gleichnamigen Paket verwenden :

mmv '*HBO_DPM*' '#1dpm#2'

Beachten Sie, dass die Argumente als Zeichenfolgen übergeben werden, sodass die Glob-Erweiterung nicht in der Shell erfolgt. Der Befehl empfängt genau zwei Argumente und sucht dann intern nach entsprechenden Dateien, ohne die Anzahl der Dateien zu begrenzen. Beachten Sie auch, dass der obige Befehl davon ausgeht, dass alle Dateien, die mit dem ersten Glob übereinstimmen, umbenannt werden sollen. Natürlich steht es Ihnen frei, genauer zu sein:

mmv 'sb_606_HBO_DPM_*' 'sb_606_dpm_#1'

Wenn Sie Dateien außerhalb des angeforderten Nummernbereichs im selben Verzeichnis haben, ist die Schleife über die weiter unten in dieser Antwort angegebenen Nummern möglicherweise besser für Sie. Sie können jedoch auch eine Folge von mmv-Aufrufen mit geeigneten Mustern verwenden:

mmv 'sb_606_HBO_DPM_0089*'       'sb_606_dpm_0089#1'    # 0089000-0089999
mmv 'sb_606_HBO_DPM_009*'        'sb_606_dpm_009#1'     # 0090000-0099999
mmv 'sb_606_HBO_DPM_01[0-5]*'    'sb_606_dpm_01#1#2'    # 0100000-0159999
mmv 'sb_606_HBO_DPM_016[0-2]*'   'sb_606_dpm_016#1#2'   # 0160000-0162999
mmv 'sb_606_HBO_DPM_01630[01]?'  'sb_606_dpm_01630#1#2' # 0163000-0163019
mmv 'sb_606_HBO_DPM_016302[0-2]' 'sb_606_dpm_016302#1'  # 0163020-0163022

Schleife über Zahlen

Wenn Sie vermeiden möchten, etwas zu installieren, oder nach Nummernbereich auswählen möchten, Übereinstimmungen außerhalb dieses Bereichs vermeiden möchten, und bereit sind, auf 74.023 Befehlsaufrufe zu warten, können Sie eine einfache Bash-Schleife verwenden:

for i in {0089000..0163022}; do mv sb_606_HBO_DPM_$i sb_606_dpm_$i; done

Dies funktioniert hier besonders gut, da es keine Lücken in der Sequenz gibt. Andernfalls möchten Sie möglicherweise überprüfen, ob die Quelldatei tatsächlich vorhanden ist.

for i in {0089000..0163022}; do
  test -e sb_606_HBO_DPM_$i && mv sb_606_HBO_DPM_$i sb_606_dpm_$i
done

Beachten Sie, dass im Gegensatz zur for ((i=89000; i<=163022; ++i))Klammererweiterung führende Nullen seit einigen Bash-Veröffentlichungen vor einigen Jahren verarbeitet werden. Eigentlich eine Änderung, die ich angefordert habe, also bin ich froh, Anwendungsfälle dafür zu sehen.

Weiterführende Literatur: Klammererweiterung auf den Bash-Infoseiten, insbesondere der Teil über {x..y[..incr]}.

Schleife über Dateien

Eine andere Möglichkeit wäre, eine Schleife über ein geeignetes Glob durchzuführen, anstatt nur über den fraglichen Integer-Bereich zu schleifen. Etwas wie das:

for i in *HBO_DPM*; do mv "$i" "${i/HBO_DPM/dpm}"; done

Dies ist wiederum ein mvAufruf pro Datei. Auch hier ist die Schleife über eine lange Liste von Elementen verteilt, aber die gesamte Liste wird nicht als Argument an einen Unterprozess übergeben, sondern intern von bash verarbeitet, sodass das Limit keine Probleme verursacht.

Weiterführende Literatur: Shell-Parametererweiterung in den Bash-Infoseiten, dokumentiert ${parameter/pattern/string}unter anderem.

Wenn Sie den Nummernkreis auf den von Ihnen angegebenen einschränken möchten, können Sie dies überprüfen:

for i in sb_606_HBO_DPM_+([0-9]); do
  if [[ "${i##*_*(0)}" -ge 89000 ]] && [[ "${i##*_*(0)}" -le 163022 ]]; then
    mv "$i" "${i/HBO_DPM/dpm}"
  fi
done

Hier ${i##pattern}wird die längste Präfixübereinstimmung patternvon entfernt $i. Dieses längste Präfix ist definiert als alles, dann ein Unterstrich, dann null oder mehr Nullen. Letzteres wird als *(0)erweitertes Glob-Muster geschrieben , das von der eingestellten extglobOption abhängt . Das Entfernen von führenden Nullen ist wichtig, um die Zahl als Basis 10 und nicht als Basis 8 zu behandeln. Das +([0-9])Argument in der Schleife ist ein weiteres erweitertes Glob, das mit einer oder mehreren Ziffern übereinstimmt, nur für den Fall, dass Sie Dateien haben, die dort gleich beginnen, aber nicht mit a enden Nummer.

MvG
quelle
Vielen Dank! Das hat wie ein Traum geklappt: für i in {0089000..0163022}; do mv sb_606_HBO_DPM_ $ i sb_606_dpm_ $ i; done - Ich musste die Dateinamenerweiterung hinzufügen, damit es funktioniert, aber es hat genau das getan, was ich wollte und ich verstehe sogar die Syntax. Vielen Dank @MvG
reich
@rich: Ich freue mich, dass ich helfen konnte - Ihnen und hoffentlich auch zukünftigen Besuchern. Vergessen Sie nicht , die nützlichste Antwort zu akzeptieren . Sie können dieses Häkchen in Zukunft jederzeit ändern, wenn sich etwas Besseres ergibt.
MvG
10

Eine Möglichkeit, das ARG_MAXLimit zu umgehen, besteht darin, die integrierte Bash-Shell zu verwenden printf:

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'

Ex.

rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long

aber

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'
rename(sb_606_HBO_DPM_0089000, sb_606_dpm_0089000)
.
.
.
rename(sb_606_HBO_DPM_0163022, sb_606_dpm_0163022)
Stahlfahrer
quelle
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

findin aktuellen Verzeichnis .für alle Dateien -type fund führen Sie die Datei umbenennen gefunden $1mit dem Ersetzen HBO_DPMmit dmp einer nach dem anderen-exec ... \;

Ersetzen echodurch mv, um die Umbenennung durchzuführen.

αғsнιη
quelle
6

Sie könnten ein kleines Python-Skript schreiben, so etwas wie:

import os
for file in os.listdir("."):
    os.rename(file, file.replace("HBO_DPM", "dpm"))

Speichern Sie diese Datei als Textdatei rename.pyin dem Ordner, in dem sich die Dateien befinden. Gehen Sie dann mit dem Terminal in diesem Ordner wie folgt vor:

python rename.py
Steinschädel
quelle
6

Sie können es Datei für Datei tun (es kann einige Zeit dauern) mit

sudo apt install util-linux  # if you don't have it already
for i in *; do rename.ul HBO_DPM dpm "$i"; done

Wie das renamein anderen Antworten verwendete Perl rename.ulhat es auch eine Option -noder --no-actzum Testen.

muclux
quelle
Ich habe Ihren Kommentar zu Zannas Antwort bearbeitet. Bitte bearbeiten Sie Zannas Antwort oder hinterlassen Sie einen Kommentar.
fosslinux
@ubashu, das war kein Kommentar zu meiner Antwort - es bezog sich auf das -nFlag, das ich zum Testen verwendet habe, und schlug vor, dass es auch in verwendet rename.ulwerden kann.
Zanna
3

Ich sehe, dass niemand meinen besten Freund sedzur Party eingeladen hat :). Die folgende forSchleife wird Ihr Ziel erreichen:

for i in sb_606_HBO_DPM*; do
  mv "$i" "$(echo $i | sed 's/HBO_DPM/dpm/')";
done

Es gibt viele Tools für einen solchen Job. Wählen Sie das für Sie verständlichste aus. Dieser ist einfach und leicht zu ändern, um diesen oder anderen Zwecken zu entsprechen ...

andrew.46
quelle
Zugegeben, in diesem speziellen Fall nicht sehr relevant, aber dies schlägt fehl, wenn einer der Dateinamen Zeilenumbrüche enthält. Ich erwähne dies, da die meisten (alle?) Anderen Antworten robust sind und mit beliebigen Dateinamen umgehen können oder nur mit dem Dateinamensschema des OP funktionieren.
Terdon
... Zeilenumbrüche, Leerzeichen, Platzhalter, ..., von denen einige durch Anführungszeichen $iin der Befehlsersetzung vermieden werden können , aber keine einfache Möglichkeit ist, eine abschließende Zeile im Dateinamen zu behandeln.
muru
3

Da wir Optionen angeben, folgt ein Perl-Ansatz. cdin das Zielverzeichnis und starte:

perl -e 'foreach(glob("sb_*")){rename $_, s/_HBO_DPM_/_dpm_/r}'

Erläuterung

  • perl -e: starte das Skript von -e.
  • foreach(glob){}: Führe aus, was auch immer in { }jedem Ergebnis des Globus steht.
  • glob("sb_*"): Gibt eine Liste aller Dateien und Verzeichnisse im aktuellen Verzeichnis zurück, deren Namen mit dem Shell-Glob übereinstimmen sb*.
  • rename $_, s/_HBO_DPM_/_dpm_/r: Perlmagie. $_ist eine spezielle Variable, die jedes Element enthält, über das wir iterieren (im foreach). Hier wird also jede Datei gefunden. s/_HBO_DPM_/_dpm_/ersetzt das erste Vorkommen von _HBO_DPM_mit _dpm_. Es $_wird standardmäßig mit jedem Dateinamen ausgeführt. Das /rbedeutet "Diese Ersetzung auf eine Kopie der Zielzeichenfolge (des Dateinamens) anwenden und die geänderte Zeichenfolge zurückgeben. Das renametut, was Sie erwarten: Es benennt Dateien um. Das Ganze wird also den aktuellen Dateinamen ( $_) in sich selbst umbenennen." _HBO_DPM_ersetzt durch _dpm_.

Sie können dasselbe wie ein erweitertes (und besser lesbares) Skript schreiben:

#! /usr/bin/env perl
use strict;
use warnings;

foreach my $fileName (glob("sb_*")){
  ## Copy the name to a new variable
  my $newName = $fileName;
  ## change the copy. $newName is now the changed version
  $newName =~ s/_HBO_DPM_/_dpm_/;
  ## rename
  rename $fileName, $newName;
}
terdon
quelle
1

Abhängig von der Art der geplanten Umbenennung ist die Verwendung von Vidir mit mehreren Zeilen möglicherweise zufriedenstellend.
In Ihrem speziellen Fall können Sie alle Zeilen in Ihrem Texteditor auswählen und den _ " HBO" -Teil der Dateinamen mit wenigen Tastenanschlägen entfernen .

Kraymer
quelle
Ja, vi hat gute Möglichkeiten zu finden und zu ersetzen.
Jasen
2
Können Sie bitte Ihre Antwort erweitern und ein Beispiel geben, mit dem Sie das OP-Ziel erreichen können vidir?
Dessert