Renommer par lots des fichiers avec Bash

101

Comment Bash peut-il renommer une série de packages pour supprimer leurs numéros de version? J'ai joué avec les deux expret %%, 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

Jeremy L
la source
1
J'ai l'intention de l'utiliser régulièrement, en tant que script à écriture unique. N'importe quel système que j'utiliserai sera bash dessus, donc je n'ai pas peur des bashismes qui sont assez pratiques.
Jeremy L
Plus généralement, voir aussi stackoverflow.com/questions/28725333/…
tripleee

Réponses:

190

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

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

Les guillemets sont nécessaires pour les noms de fichiers avec des espaces.

richq
la source
1
Je l'aime aussi, mais il utilise des fonctionnalités spécifiques à bash ... Je ne les utilise normalement pas en faveur de sh "standard".
Diego Sevilla
2
Pour les trucs interactifs jetables sur une ligne, j'utilise toujours ceci au lieu de sed-fu. Si c'était un script, oui, évitez les bashismes.
richq
Un problème que j'ai trouvé avec le code: que se passe-t-il si les fichiers ont déjà été renommés et que je l'exécute à nouveau? Je reçois des avertissements pour essayer de passer dans un sous-répertoire de lui-même.
Jeremy L
1
Pour éviter les erreurs de réexécution, vous pouvez utiliser le même modèle dans la partie " .pkg", c'est-à-dire "for i in * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; terminé ". Mais les erreurs sont suffisamment anodines (déplacement vers le même fichier).
richq
3
Ce n'est pas une solution bash, mais si perl est installé, il fournit la commande de changement de nom pratique pour le changement de nom par lots, faites simplementrename "s/-[0-9.]*//" *.pkg
Rufus
33

Si tous les fichiers sont 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 la fin | shafin de vérifier que la commande fait ce que vous voulez.

Pour récurer 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 des expressions régulières est constituée de crochets précédés d'une barre oblique inverse, \(et \), plutôt que de simples crochets (et ).

Diomidis Spinellis
la source
Pardonnez ma naïveté, mais échapper aux parenthèses avec des contre-obliques ne les ferait-il pas être traités comme des caractères littéraux?
devios1
2
Dans sed, la séquence de regroupement des expressions régulières est (, plutôt qu'une simple parenthèse.
Diomidis Spinellis
n'a pas fonctionné pour moi, je serais heureux d'obtenir votre aide .... Le suffixe du fichier FROM est ajouté au fichier TO: $ find. -type d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> OUTPUT >>>> mv: renommer ./base/src/main/java/com/anysoftkeyboard/base/dictionaries en ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : Aucun fichier ou répertoire de ce type
Vitali Pom
Vous semblez manquer la barre oblique inverse entre crochets.
Diomidis Spinellis
1
Ne pas utiliser lsdans les scripts. La première variété peut être obtenue avec printf '%s\n' * | sed ...et avec un peu de créativité, vous pouvez probablement vous en débarrasser sed. Bash printfpropose un '%q'code de format qui pourrait être utile si vous avez des noms de fichiers contenant des métacaractères shell littéraux.
triple
10

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 sont dans le répertoire courant. Sinon, essayez d'utiliser find comme indiqué ci-dessus par Javier.

ÉDITER : De plus, cette version n'utilise aucune fonctionnalité spécifique à bash, comme d'autres ci-dessus, ce qui vous conduit à plus de portabilité.

Diego Séville
la source
Ils sont tous dans le même répertoire. Que pensez-vous de la réponse de rq?
Jeremy L
J'aime ne pas utiliser de fonctionnalités spécifiques à bash, mais plutôt sh standard.
Diego Sevilla
1
J'ai supposé que c'était pour un changement de nom rapide, pas pour écrire la prochaine génération multiplateforme, application d'entreprise portable ;-)
richq
1
Haha, assez juste ... Juste hors d'un homme soucieux de la portabilité comme moi :)
Diego Sevilla
Celui-ci ne tient pas compte du troisième exemple: xorg-libXrandr (trait d'union utilisé dans le nom).
Jeremy L
5

Voici un quasi-équivalent POSIX de la réponse actuellement acceptée . Cela échange l' ${variable/substring/replacement}extension de paramètre uniquement Bash contre une extension 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 variableavec n'importe quel suffixe qui correspond à patternsupprimé. (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'il soit peut-être trompeur. Ce n'est pas une expression régulière, mais un modèle global; donc cela ne veut pas dire "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 le match le plus court possible, pas le plus long. (Bash propose ##et %%pour couper le préfixe ou le suffixe le plus long possible, plutôt que le plus court.)

tripleee
la source
5

Nous pouvons supposer qu'il sedest disponible sur n'importe quel * nix, mais nous ne pouvons pas être sûrs qu'il prendra en charge la sed -ngénération de commandes mv. ( REMARQUE: seul GNU le sedfait.)

Même ainsi, bash builtins et sed, nous pouvons rapidement créer 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:

Puisque mvne crée pas automatiquement les dossiers cibles, nous ne pouvons pas utiliser notre version initiale desedrename .

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

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

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 (s) dossier (s) cible (s) pour un modèle sed / rename qui inclut une nouvelle structure de dossier.

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 prenant en charge les dossiers ...

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 non plus 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 modèles de regex sed

Les règles de modèle sed régulières s'appliquent dans ce script, ces modèles ne sont pas PCRE (Perl Compatible Regular Expressions). Vous pourriez avoir sed étendu la syntaxe des expressions régulières, en utilisant soit sed -roused -E fonction de votre plate-forme.

Voir la compatibilité POSIX man re_formatpour une description complète des modèles d'expression régulière sed basiques et étendus.

ocodo
la source
4

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 regex pour cela, ce qui demande un peu d'étude, mais cela en vaut la peine.

Simon Katan
la source
Je suis d'accord. Pour une chose occasionnelle, utiliser foret sedetc. est bien, mais c'est beaucoup plus simple et plus rapide à utiliser renamesi vous vous trouvez souvent à le faire.
argentum2f
Est-ce que vous échappez à vos règles?
Big Money
Le problème est que ce n'est pas portable; non seulement toutes les plates-formes n'ont pas de renamecommande; en outre, certains auront une commande différente rename avec des fonctionnalités, une syntaxe et / ou des options différentes. Cela ressemble au Perl renamequi est assez populaire; mais la réponse devrait vraiment clarifier cela.
tripleee le
3

meilleure utilisation sedpour 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

trouver l'expression régulière est laissé comme un exercice (comme c'est le cas avec les noms de fichiers qui incluent des espaces)

Javier
la source
Merci: Les espaces ne sont pas utilisés dans les noms.
Jeremy L
1

Cela semble fonctionner en supposant que

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

enlevez le .pkg, puis enlevez - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
Steve B.
la source
Certains packages ont un trait d'union dans leur nom (voir le troisième exemple). Cela a-t-il un impact sur votre code?
Jeremy L
1
Vous pouvez exécuter plusieurs expressions sed en utilisant -e. Par exemple sed -e 's/\.pkg//g' -e 's/-.*//g'.
tacotuesday
N'analysez pas la lssortie et ne citez pas vos variables. Voir aussi shellcheck.net pour diagnostiquer les problèmes courants et les antipatterns dans les scripts shell.
tripleee
1

J'ai eu plusieurs *.txtfichiers à renommer comme .sqldans le même dossier. ci-dessous a travaillé pour moi:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
Kumar Mashalkar
la source
2
Vous pouvez améliorer votre réponse en la résumant au cas d'utilisation réel du PO.
dakab
Cela pose plusieurs problèmes ainsi qu'une erreur de syntaxe apparente. Voir shellcheck.net pour un début.
tripleee
0

Merci pour ces réponses. J'ai aussi eu une sorte de problème. Déplacement des fichiers .nzb.queued vers des fichiers .nzb. Il y avait des espaces et d'autres cruels 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 chaînes entre guillemets. Cela évite également de créer une boucle dans le script shell car cela est déjà fait par sed.

Jerry Jacobs
la source
Il convient de noter que cela n'est vrai que pour GNU sed. Sur les BSD, sed n'interprétera pas l' -noption de la même manière.
ocodo