web-dev-qa-db-fra.com

Renommer des fichiers par lots avec Bash

Comment Bash peut-il renommer une série de packages pour supprimer leurs numéros de version? J'ai joué avec expr et %%, en vain.

Exemples:

Xft2-2.1.13.pkg devient Xft2.pkg

jasper-1.900.1.pkg devient jasper.pkg

xorg-libXrandr-1.2.3.pkg devient xorg-libXrandr.pkg

93
Jeremy L

Vous pouvez utiliser la fonction d'extension des paramètres de bash

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

Des citations sont nécessaires pour les noms de fichiers avec des espaces.

177
richq

Si tous les fichiers se trouvent dans le même répertoire, la séquence

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

fera votre travail. La commande sed créera une séquence de commandes mv, que vous pourrez ensuite diriger vers le shell. Il est préférable d'exécuter d'abord le pipeline sans le _ | sh afin de vérifier que la commande fait ce que vous voulez.

Pour récuser dans plusieurs répertoires, utilisez quelque chose comme

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

Notez que dans sed la séquence de regroupement d'expressions régulières est des crochets précédés d'une barre oblique inverse, \( et \), plutôt que des crochets simples ( et ).

32

Je vais faire quelque chose comme ça:

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

supposé que tous vos fichiers se trouvent dans le répertoire courant. Sinon, essayez d'utiliser find comme indiqué ci-dessus par Javier.

EDIT : De plus, cette version n'utilise pas de fonctionnalités spécifiques à bash, comme les autres ci-dessus, ce qui vous amène à plus de portabilité.

10
Diego Sevilla

Voici un quasi-équivalent POSIX du réponse actuellement acceptée . Cela échange le Bash uniquement ${variable/substring/replacement} extension des paramètres pour celui qui est disponible dans n'importe quel shell compatible Bourne.

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

L'expansion des paramètres ${variable%pattern} produit la valeur de variable avec tout suffixe correspondant à pattern supprimé. (Il y a aussi ${variable#pattern} pour supprimer un préfixe.)

J'ai gardé le sous-modèle -[0-9.]* de la réponse acceptée bien qu'elle soit peut-être trompeuse. Ce n'est pas une expression régulière, mais un motif global; cela ne signifie donc pas "un tiret suivi de zéro ou plusieurs nombres ou points". Au lieu de cela, cela signifie "un tiret, suivi d'un nombre ou d'un point, suivi de n'importe quoi". Le "n'importe quoi" sera la correspondance la plus courte possible, pas la plus longue. (Bash propose ## et %% pour rogner le préfixe ou le suffixe le plus long possible, plutôt que le plus court.)

4
tripleee

Nous pouvons supposer que sed est disponible sur n'importe quel * nix, mais nous ne pouvons pas être sûrs qu'il supportera sed -n pour générer des commandes mv. (REMARQUE: Seulement GNU sed fait cela.)

Même ainsi, bash builtins et sed, nous pouvons rapidement concocter une fonction Shell pour ce faire.

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
}

Usage

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

Création de dossiers cibles:

Étant donné que mv ne crée pas automatiquement les dossiers cibles, nous ne pouvons pas utiliser notre version initiale de sedrename.

C'est un assez petit changement, donc ce serait bien d'inclure cette fonctionnalité:

Nous aurons besoin d'une fonction utilitaire, abspath (ou chemin absolu) car bash n'a pas cette build in.

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

Une fois que nous avons cela, nous pouvons générer le ou les dossiers cibles pour un modèle sed/rename qui inclut une nouvelle structure de dossiers.

Cela garantira que nous connaissons les noms de nos dossiers cibles. Lorsque nous renommerons, nous devrons l'utiliser sur le nom du fichier cible.

# 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"

Voici le script complet du dossier ...

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
}

Bien sûr, cela fonctionne toujours lorsque nous n'avons pas de dossiers cibles spécifiques.

Si nous voulions mettre toutes les chansons dans un dossier, ./Beethoven/ nous pouvons le faire:

Usage

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

Tour bonus ...

Utilisation de ce script pour déplacer des fichiers de dossiers dans un seul dossier:

En supposant que nous voulions rassembler tous les fichiers correspondants et les placer dans le dossier actuel, nous pouvons le faire:

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

Remarque sur les schémas d'expression rationnelle sed

Les règles de modèle sed standard s'appliquent dans ce script, ces modèles ne sont pas PCRE (Perl Compatible Regular Expressions). Vous pourriez avoir sed la syntaxe d'expression régulière étendue, en utilisant soit sed -r ou sed -E selon votre plateforme.

Voir la conformité POSIX man re_format pour une description complète des modèles regexp basiques et étendus de sed.

3
ocodo

mieux utiliser sed pour cela, quelque chose comme:

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

comprendre l'expression régulière est laissé comme un exercice (tout comme pour les noms de fichiers qui incluent des espaces)

3
Javier

Je trouve que renommer est un outil beaucoup plus simple à utiliser pour ce genre de chose. Je l'ai trouvé sur Homebrew pour OSX

Pour votre exemple, je ferais:

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

Le "s" signifie substitut. Le formulaire est s/searchPattern/replacement/files_to_apply. Vous devez utiliser l'expression régulière pour cela, ce qui nécessite un peu d'étude, mais cela en vaut la peine.

3
Simon Katan

J'ai eu plusieurs *.txt fichiers à renommer .sql dans le même dossier. ci-dessous a fonctionné pour moi:

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

Cela semble fonctionner en supposant que

  • tout se termine avec $ pkg
  • votre numéro de version commence toujours par un "-"

retirer le .pkg, puis retirer - ..

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

Merci pour ces réponses. J'ai aussi eu une sorte de problème. Déplacement de fichiers .nzb.queued vers des fichiers .nzb. Il y avait des espaces et d'autres creux dans les noms de fichiers et cela a résolu mon problème:

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

Il est basé sur la réponse de Diomidis Spinellis.

L'expression régulière crée un groupe pour le nom de fichier entier et un groupe pour la partie avant .nzb.queued, puis crée une commande de déplacement du shell. Avec les cordes citées. Cela évite également de créer une boucle dans le script Shell car cela est déjà fait par sed.

0
Jerry Jacobs