Lot renommer des fichiers avec Bash
-
03-07-2019 - |
Question
Comment Bash peut-il renommer une série de packages pour supprimer leurs numéros de version? Je me suis amusé 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
La solution
Vous pouvez utiliser la fonctionnalité d'expansion 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.
Autres conseils
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 va créer une séquence de commandes mv , que vous pouvez ensuite diriger vers le shell. Il est préférable de commencer par exécuter le pipeline sans le | sh
afin de vérifier que la commande fait ce que vous voulez.
Pour recurse dans plusieurs annuaires, 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 constituée de crochets précédés d'une barre oblique inversée, \ (
et \)
, plutôt que de simples crochets. (
et )
.
Je vais faire quelque chose comme ça:
for file in *.pkg ; do
mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done
suppose que tous vos fichiers sont dans le répertoire en cours. Sinon, essayez d’utiliser find comme conseillé par Javier ci-dessus.
MODIFIER : cette version n'utilise aucune fonctionnalité spécifique à bash, contrairement à ce qui précède, ce qui vous permet de bénéficier d'une plus grande portabilité.
Voici un équivalent POSIX de la réponse actuellement acceptée . Ceci échange le développement du paramètre $ {variable / sous-chaîne / remplacement}
réservé à Bash par un développement disponible dans tout shell compatible avec Bourne.
for i in ./*.pkg; do
mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done
Le paramètre expansion $ {variable% pattern}
produit la valeur de variable
avec tout suffixe correspondant au modèle
supprimé. (Il existe également $ {variable # motif}
pour supprimer un préfixe.)
J'ai gardé le sous-modèle - [0-9.] *
de la réponse acceptée bien que ce soit peut-être trompeur. Ce n'est pas une expression régulière, mais un motif glob; cela ne signifie donc pas "un tiret suivi de zéro ou plusieurs chiffres ou points". Au lieu de cela, cela signifie "un tiret, suivi d'un nombre ou d'un point, suivi de rien". 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.)
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
déterminer l'expression régulière est laissé comme un exercice (comme pour les noms de fichiers qui incluent des espaces)
Nous pouvons supposer que sed
est disponible sur n'importe quel * nix, mais nous ne pouvons pas être sûrs
cela supportera sed -n
pour générer des commandes mv. ( REMARQUE: seulement sed
GNU le fait).
Même dans ce cas, nous pouvons rapidement créer une fonction shell pour le 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: 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
sed_pattern files..."
fi
}
Utilisation
abspath () { case "$1" in
/*)printf "%s\n" "$1";;
*)printf "%s\n" "$PWD/$1";;
esac; }
Création des dossiers cible:
Étant donné que mv
ne crée pas automatiquement les dossiers cibles que nous ne pouvons pas utiliser.
notre version initiale de sedrename
.
Il s’agit d’un changement relativement modeste, il serait donc bon d’inclure cette fonctionnalité:
Nous aurons besoin d'une fonction utilitaire, abspath
(ou chemin absolu) depuis bash
n'a pas cette construction.
# 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"
Une fois que nous avons cela, nous pouvons générer le ou les dossiers cibles d’un modèle sed / rename qui inclut une nouvelle structure de dossiers.
Cela nous permettra de connaître les noms de nos dossiers cibles. quand nous renommer nous devrons l’utiliser sur le nom du fichier cible.
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: 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
sed_pattern files..."
fi
}
Voici le script prenant en charge les dossiers complets ...
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
Bien sûr, cela fonctionne quand on n'a pas de dossiers cibles spécifiques aussi.
Si nous voulions mettre tous les morceaux dans un dossier, ./ Beethoven /
, nous pouvons le faire:
Utilisation
<*>Tour bonus ...
Utilisation de ce script pour déplacer des fichiers de dossiers vers un seul dossier:
En supposant que nous voulions rassembler tous les fichiers correspondants et les placer dans le dossier en cours, nous pouvons le faire:
<*>Note sur les modèles regex sed
Les règles habituelles relatives aux modèles sed s'appliquent dans ce script. Ces modèles ne sont pas
PCRE (expressions régulières compatibles Perl). Vous auriez pu sed
syntaxe d'expression régulière étendue, en utilisant sed -r
ou sed -E
en fonction de votre plate-forme.
Voir le man re_format
conforme à POSIX pour une description complète de
Modèles regexp de base et étendus.
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 nécessite un peu d’étude, mais cela en vaut la peine.
Cela semble fonctionner en supposant que
- tout se termine par $ pkg
- votre numéro de version commence toujours par un "- -"
éliminez le .pkg, puis éliminez - ..
for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
J'avais plusieurs fichiers *. txt
à 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
Merci pour cette réponse. 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 autres noms 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 rationnelle crée un groupe pour le nom de fichier complet et un groupe pour la partie précédant .nzb.queued, puis crée une commande de déplacement de shell. Avec les cordes citées. Cela évite également de créer une boucle dans un script car cela est déjà fait par sed.