Batch-Umbenennungsdateien mit Bash

101

Wie kann Bash eine Reihe von Paketen umbenennen, um deren Versionsnummern zu entfernen? Ich habe mit beiden herumgespielt exprund %%ohne Erfolg.

Beispiele:

Xft2-2.1.13.pkg wird Xft2.pkg

jasper-1.900.1.pkg wird jasper.pkg

xorg-libXrandr-1.2.3.pkg wird xorg-libXrandr.pkg

Jeremy L.
quelle
1
Ich beabsichtige, dies regelmäßig als Skript zu verwenden, das einmal viel geschrieben hat. Jedes System, das ich verwenden werde, wird Bash haben, also habe ich keine Angst vor den Bashismen, die ziemlich praktisch sind.
Jeremy L
Allgemeiner siehe auch stackoverflow.com/questions/28725333/…
Tripleee

Antworten:

190

Sie können die Parametererweiterungsfunktion von bash verwenden

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

Für Dateinamen mit Leerzeichen werden Anführungszeichen benötigt.

richq
quelle
1
Ich mag es auch, aber es verwendet Bash-spezifische Funktionen ... Normalerweise verwende ich sie nicht zugunsten von "Standard" sh.
Diego Sevilla
2
Für einzeilige einzeilige interaktive Dinge verwende ich immer diese anstelle von Sed-Fu. Wenn es ein Skript wäre, vermeiden Sie Bashismen.
Richq
Ein Problem, das ich mit dem Code gefunden habe: Was passiert, wenn die Dateien bereits umbenannt wurden und ich sie erneut ausführe? Ich erhalte Warnungen, wenn ich versuche, in ein Unterverzeichnis von sich selbst zu wechseln.
Jeremy L
1
Um Fehler beim erneuten Ausführen zu vermeiden, können Sie dasselbe Muster im Teil " .pkg" verwenden, dh "für i in * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; erledigt ". Die Fehler sind jedoch harmlos genug (Verschieben in dieselbe Datei).
Richq
3
Keine Bash-Lösung, aber wenn Sie Perl installiert haben, bietet es den praktischen Umbenennungsbefehl für das Batch-Umbenennen. Machen Sie einfachrename "s/-[0-9.]*//" *.pkg
Rufus
33

Wenn sich alle Dateien im selben Verzeichnis befinden, die Reihenfolge

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

wird deinen Job machen. Der Befehl sed erstellt eine Folge von mv- Befehlen, die Sie dann in die Shell weiterleiten können. Führen Sie die Pipeline am besten zuerst ohne Nachlauf aus | sh, um zu überprüfen, ob der Befehl das tut, was Sie möchten.

Um mehrere Verzeichnisse zu durchsuchen, verwenden Sie so etwas wie

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

Beachten Sie, dass in sed die Gruppierungssequenz für reguläre Ausdrücke Klammern ist, denen ein Backslash vorangestellt ist, \(und \)nicht einzelne Klammern (und ).

Diomidis Spinellis
quelle
Verzeihen Sie meine Naivität, aber würden sie nicht als wörtliche Zeichen behandelt, wenn Sie den Klammern nicht mit umgekehrten Schrägstrichen entkommen?
Devios1
2
In sed ist die Gruppierungssequenz für reguläre Ausdrücke (und nicht eine einzelne Klammer).
Diomidis Spinellis
hat bei mir nicht funktioniert, würde sich über Ihre Hilfe freuen .... Das Suffix der FROM-Datei wird an die TO-Datei angehängt: $ find. -Typ d | sed -ns /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p '| sh >> >>>>> AUSGABE >>>> mv: Benennen Sie ./base/src/main/java/com/anysoftkeyboard/base/dictionaries in ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries um : Keine solche Datei oder Verzeichnis
Vitali Pom
Sie scheinen den Backslash in den Klammern zu vermissen.
Diomidis Spinellis
1
Nicht lsin Skripten verwenden. Die erste Sorte kann mit printf '%s\n' * | sed ...und mit etwas Kreativität erreicht werden, die Sie wahrscheinlich auch loswerden können sed. Bash's printfbietet einen '%q'Formatcode, der nützlich sein kann, wenn Sie Dateinamen haben, die wörtliche Shell-Metazeichen enthalten.
Tripleee
10

Ich mache so etwas:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

Angenommen, alle Ihre Dateien befinden sich im aktuellen Verzeichnis. Wenn nicht, versuchen Sie, find wie oben von Javier empfohlen zu verwenden.

BEARBEITEN : Außerdem verwendet diese Version keine Bash-spezifischen Funktionen wie die oben genannten, was zu mehr Portabilität führt.

Diego Sevilla
quelle
Sie befinden sich alle im selben Verzeichnis. Was halten Sie von der Antwort von rq?
Jeremy L
Ich verwende nicht gerne Bash-spezifische Funktionen, sondern eher Standard-Sh.
Diego Sevilla
1
Ich nahm an, dass dies für eine schnelle Umbenennung war, nicht für das Schreiben der plattformübergreifenden, tragbaren Unternehmensanwendung der nächsten Generation
;-)
1
Haha, fair genug ... Nur aus einem tragbarkeitsorientierten Mann wie mir heraus :)
Diego Sevilla
Dieses dritte Beispiel ist nicht berücksichtigt: xorg-libXrandr (im Namen verwendeter Bindestrich).
Jeremy L
5

Hier ist ein POSIX, das der aktuell akzeptierten Antwort nahezu entspricht . Dadurch wird die Nur-Bash- ${variable/substring/replacement}Parametererweiterung gegen eine ausgetauscht, die in jeder Bourne-kompatiblen Shell verfügbar ist.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

Die Parametererweiterung ${variable%pattern}erzeugt den Wert von variablemit einem Suffix, das mit patternentfernt übereinstimmt . (Es gibt auch ${variable#pattern}ein Präfix zu entfernen.)

Ich habe das Untermuster -[0-9.]*von der akzeptierten Antwort ferngehalten , obwohl es vielleicht irreführend ist. Es ist kein regulärer Ausdruck, sondern ein Globusmuster. es bedeutet also nicht "ein Bindestrich gefolgt von null oder mehr Zahlen oder Punkten". Stattdessen bedeutet es "ein Bindestrich, gefolgt von einer Zahl oder einem Punkt, gefolgt von irgendetwas". Das "irgendetwas" wird das kürzestmögliche Spiel sein, nicht das längste. (Bash-Angebote ##und %%zum Trimmen des längstmöglichen Präfixes oder Suffixes anstelle des kürzesten.)

Tripleee
quelle
4

Wir können davon ausgehen, dass sedes auf jedem * nix verfügbar ist, aber wir können nicht sicher sein, ob es sed -ndas Generieren von mv-Befehlen unterstützt. ( HINWEIS:sed Dies wird nur von GNU durchgeführt .)

Trotzdem können wir schnell eine Shell-Funktion entwickeln, um dies zu tun.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Verwendung

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Zielordner erstellen:

Da mvZielordner nicht automatisch erstellt werden, können wir unsere ursprüngliche Version von nicht verwenden sedrename.

Es ist eine ziemlich kleine Änderung, daher wäre es schön, diese Funktion aufzunehmen:

Wir benötigen eine Utility-Funktion abspath(oder einen absoluten Pfad), da bash diese Funktion nicht integriert hat.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Sobald wir das haben, können wir die Zielordner für ein sed / rename-Muster generieren, das eine neue Ordnerstruktur enthält.

Dadurch wird sichergestellt, dass wir die Namen unserer Zielordner kennen. Wenn wir umbenennen, müssen wir es für den Namen der Zieldatei verwenden.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Hier ist das vollständige Ordner-fähige Skript ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Natürlich funktioniert es immer noch, wenn wir keine spezifischen Zielordner haben.

Wenn wir alle Songs in einen Ordner legen wollten, können ./Beethoven/wir dies tun:

Verwendung

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Bonusrunde ...

Verwenden dieses Skripts zum Verschieben von Dateien aus Ordnern in einen einzelnen Ordner:

Angenommen, wir wollten alle übereinstimmenden Dateien sammeln und im aktuellen Ordner ablegen, dann können wir Folgendes tun:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Hinweis zu sed regex-Mustern

In diesem Skript gelten die Regeln für reguläre sed-Muster. Diese Muster sind keine PCRE (Perl Compatible Regular Expressions). Sie hätten die erweiterte Syntax für reguläre Ausdrücke entweder sed -roder sed -E abhängig von Ihrer Plattform verwenden können.

man re_formatEine vollständige Beschreibung der sed-Basis- und erweiterten Regexp-Muster finden Sie in der POSIX-Konformität .

ocodo
quelle
4

Ich finde, dass das Umbenennen ein viel einfacheres Werkzeug für solche Dinge ist. Ich habe es auf Homebrew für OSX gefunden

Für Ihr Beispiel würde ich tun:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

Das 's' bedeutet Ersatz. Das Formular lautet s / searchPattern / replace / files_to_apply. Sie müssen dafür Regex verwenden, was ein wenig Studium erfordert, aber die Mühe lohnt sich.

Simon Katan
quelle
Genau. Für eine gelegentliche Sache, mit forund sedusw. ist in Ordnung, aber es ist viel einfacher und schneller zu nur nutzen , renamewenn Sie sich diese oft finden zu tun.
argentum2f
Entkommst du Perioden?
Big Money
Das Problem ist, dass dies nicht portabel ist; Nicht nur, dass nicht alle Plattformen einen renameBefehl haben. Darüber hinaus verfügen einige über einen anderen rename Befehl mit unterschiedlichen Funktionen, Syntax und / oder Optionen. Dies sieht aus wie das Perl, renamedas ziemlich beliebt ist; aber die Antwort sollte dies wirklich klarstellen.
Tripleee
3

Bessere Verwendung seddafür, so etwas wie:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

Das Herausfinden des regulären Ausdrucks bleibt als Übung (ebenso wie der Umgang mit Dateinamen, die Leerzeichen enthalten).

Javier
quelle
Danke: Leerzeichen werden in den Namen nicht verwendet.
Jeremy L
1

Dies scheint unter der Annahme zu funktionieren

  • alles endet mit $ pkg
  • Ihre Versionsnummer beginnt immer mit einem "-"

.pkg abstreifen, dann abstreifen - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
Steve B.
quelle
Es gibt einige Pakete, deren Name einen Bindestrich enthält (siehe drittes Beispiel). Hat dies Auswirkungen auf Ihren Code?
Jeremy L
1
Sie können mehrere sed-Ausdrücke mit ausführen -e. ZB sed -e 's/\.pkg//g' -e 's/-.*//g'.
Tacotuesday
Analysieren Sie die lsAusgabe nicht und zitieren Sie Ihre Variablen nicht. Siehe auch shellcheck.net zur Diagnose häufiger Probleme und Antimuster in Shell-Skripten.
Tripleee
1

Ich musste mehrere *.txtDateien wie .sqlim selben Ordner umbenennen . unten arbeitete für mich:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
Kumar Mashalkar
quelle
2
Sie können Ihre Antwort verbessern, indem Sie sie auf den tatsächlichen Anwendungsfall des OP abstrahieren.
Dakab
Dies hat mehrere Probleme sowie einen offensichtlichen Syntaxfehler. Siehe shellcheck.net für einen Anfang.
Tripleee
0

Vielen Dank für diese Antworten. Ich hatte auch ein Problem. Verschieben von .nzb.queued-Dateien in .nzb-Dateien. Es hatte Leerzeichen und andere Cruft in den Dateinamen und dies löste mein Problem:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

Es basiert auf der Antwort von Diomidis Spinellis.

Der reguläre Ausdruck erstellt eine Gruppe für den gesamten Dateinamen und eine Gruppe für den Teil vor .nzb.queued und erstellt dann einen Shell-Verschiebungsbefehl. Mit den angegebenen Zeichenfolgen. Dadurch wird auch vermieden, dass eine Schleife im Shell-Skript erstellt wird, da dies bereits von sed ausgeführt wird.

Jerry Jacobs
quelle
Zu beachten ist, dass dies nur für GNU sed gilt. Bei BSDs interpretiert sed die -nOption nicht auf die gleiche Weise.
ocodo