web-dev-qa-db-de.com

Batch-Umbenennung von Dateien mit Bash

Wie kann Bash eine Reihe von Paketen umbenennen, um deren Versionsnummern zu entfernen? Ich habe sowohl mit expr als auch mit %% Gespielt, ohne Erfolg.

Beispiele:

Xft2-2.1.13.pkg Wird zu Xft2.pkg

jasper-1.900.1.pkg Wird zu jasper.pkg

xorg-libXrandr-1.2.3.pkg Wird zu xorg-libXrandr.pkg

93
Jeremy L

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 sind Anführungszeichen erforderlich.

177
richq

Wenn sich alle Dateien im selben Verzeichnis befinden, wird die Reihenfolge festgelegt

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

werde deinen Job machen. Der Befehl sed erstellt eine Folge von mv Befehlen, die Sie dann in die Shell leiten können. Es ist am besten, zuerst die Pipeline ohne den nachfolgenden | sh auszuführen, um zu überprüfen, ob der Befehl das tut, was Sie wollen.

Um durch mehrere Verzeichnisse zu rekursieren, 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 der Gruppierungssequenz für reguläre Ausdrücke Klammern mit einem Backslash \( und \) und keine einzelnen Klammern ( vorangestellt sind. und ).

32

Ich mache so etwas:

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

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

EDIT : Diese Version verwendet auch keine bash-spezifischen Funktionen, wie andere oben, was zu mehr Portabilität führt.

10
Diego Sevilla

Hier ist eine POSIX-Entsprechung von derzeit akzeptierte Antwort . Dies handelt die Bash-only ${variable/substring/replacement} Parametererweiterung für einen, der 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 variable, wobei jedes Suffix, das mit pattern übereinstimmt, entfernt wird. (Es gibt auch ${variable#pattern}, um ein Präfix zu entfernen.)

Ich behielt das Untermuster -[0-9.]* aus der akzeptierten Antwort, obwohl es vielleicht irreführend ist. Es ist kein regulärer Ausdruck, sondern ein Glob-Muster. es bedeutet also nicht "ein Strich 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" ist die kürzestmögliche Übereinstimmung, nicht die längste. (Bash bietet ## und %% zum Trimmen des längsten möglichen Präfixes oder Suffixes anstelle des kürzesten.)

4
tripleee

Wir können davon ausgehen, dass sed auf jedem * nix verfügbar ist, aber wir können nicht sicher sein, dass es sed -n Zur Generierung von mv-Befehlen unterstützt. (HINWEIS: Nur GNU sed macht das.)

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

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls [email protected]); 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 mv nicht automatisch Zielordner erstellt, können wir unsere ursprüngliche Version von sedrename nicht verwenden.

Da es sich um eine relativ kleine Änderung handelt, wäre es schön, diese Funktion einzuschließen:

Wir benötigen eine Utility-Funktion, abspath (oder einen absoluten Pfad), da bash diesen Build nicht enthält.

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

Sobald wir das haben, können wir den/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 Zieldateinamen 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 ordnerbezogene Skript ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls [email protected]); 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 auch dann noch, wenn wir keine spezifischen Zielordner haben.

Wenn wir alle Songs in einen Ordner legen wollten, ./Beethoven/ Können 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 zusammenstellen 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 nicht PCRE (Perl-kompatible reguläre Ausdrücke). Sie könnten die Syntax für reguläre Ausdrücke erweitert haben und entweder sed -r Oder sed -E Verwenden, abhängig von Ihrer Plattform.

Siehe POSIX-konformes man re_format Für eine vollständige Beschreibung der grundlegenden und erweiterten regulären Ausdrucksmuster.

3
ocodo

benutze dazu besser sed, 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 erhalten (ebenso wie der Umgang mit Dateinamen, die Leerzeichen enthalten).

3
Javier

Ich finde, dass das Umbenennen ein viel einfacher zu verwendendes Werkzeug ist. Ich habe es auf Homebrew für OSX gefunden

Für dein Beispiel würde ich tun:

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

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

3
Simon Katan

Ich hatte mehrere *.txt - Dateien im selben Ordner, die in .sql Umbenannt werden mussten. unten hat für mich gearbeitet:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
1
Kumar Mashalkar

Dies scheint unter der Annahme zu funktionieren

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

entfernen Sie die .pkg, und entfernen Sie dann - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
1
Steve B.

Vielen Dank für diese Antworten. Ich hatte auch ein Problem. Verschieben von .nzb.queued-Dateien in .nzb-Dateien. Die Dateinamen enthielten Leerzeichen und andere Beeinträchtigungen. 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.

Die Regex erstellt eine Gruppe für den gesamten Dateinamen und eine Gruppe für den Teil vor .nzb.queued und erstellt dann einen Shell-Verschiebebefehl. Mit den angegebenen Zeichenfolgen. Dadurch wird auch vermieden, dass eine Schleife im Shell-Skript erstellt wird, da dies bereits von sed durchgeführt wird.

0
Jerry Jacobs