Renommer plusieurs fichiers en fonction du modèle sous Unix

248

Il y a plusieurs fichiers dans un répertoire qui commencent par un préfixe fgh, par exemple:

fghfilea
fghfileb
fghfilec

Je veux les renommer tous pour commencer par un préfixe jkl. Existe-t-il une seule commande pour le faire au lieu de renommer chaque fichier individuellement?

jww
la source
1
vérifier ici: theunixshell.blogspot.com/2013/01/…
Vijay
Question connexe: meilleure façon de renommer des fichiers en fonction de plusieurs modèles .
Michael Le Barbier Grünewald

Réponses:

289

Il existe plusieurs façons, mais l'utilisation renamesera probablement la plus simple.

En utilisant une version de rename:

rename 's/^fgh/jkl/' fgh*

En utilisant une autre version de rename(identique à la réponse de Judy2K ):

rename fgh jkl fgh*

Vous devriez vérifier la page de manuel de votre plate-forme pour voir lequel des éléments ci-dessus s'applique.

Stephan202
la source
7
+1 Je ne savais même pas comment renommer ... Maintenant je peux arrêter d'utiliser une boucle for avec mv et sed ... Merci!
balpha
2
Vous renamevous connectez à un autre, puis vous montrez la syntaxe pour unixhelp.ed.ac.uk/CGI/man-cgi?rename est l'autre
Hasturkun
7
Non présent sur tous les systèmes * nix. Pas sur Max OS X pour un, et aucun paquet dans fink pour l'obtenir. Je n'ai pas regardé MacPorts.
dmckee --- chaton ex-modérateur
6
AFAICT, renamesemble être un script ou un utilitaire spécifique à Linux. Si vous vous souciez de la portabilité, veuillez continuer à utiliser les sedboucles et ou un script Perl en ligne.
D.Shawley
33
brew install renamesur OS X :)
sam
117

Voici comment sedet mvpeuvent être utilisés ensemble pour renommer:

for f in fgh*; do mv "$f" $(echo "$f" | sed 's/^fgh/jkl/g'); done

Selon le commentaire ci-dessous, si les noms de fichiers contiennent des espaces, les guillemets peuvent devoir entourer la sous-fonction qui renvoie le nom pour déplacer les fichiers vers:

for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
nik
la source
1
Très proche. Notez que vous souhaitez uniquement faire correspondre la première occurrence de fgh: 's / ^ fgh / jkl / g' (le signe insertion fait toute la différence).
Stephan202
2
Juste pour des raisons de précision ... Vous voulez dire "fgh au début du nom", pas "la première occurrence de fgh". / ^ fgh / correspondra à "fghi", mais pas à "efgh".
Dave Sherohman
@Stephan, C'était une faute de frappe de ma part (corrigé).
nik
5
Si vous n'avez pas accès à "renommer" cela fonctionne très bien. Le code peut nécessiter des guillemets si les noms de vos fichiers contiennent des espaces. for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
Dave Nelson
1
@nik sans guillemets renommer cette liste de fichiers jetterait une erreur: touch fghfilea fghfileb fghfilec fghfile\ d. Je suggère de prendre en considération les remarques de @DaveNelson.
luissquall
75

renommer peut ne pas être dans tous les systèmes. donc si vous ne l'avez pas, utilisez le shell de cet exemple dans le shell bash

for f in fgh*; do mv "$f" "${f/fgh/xxx}";done
ghostdog74
la source
1
Dans cette solution, la sedcommande n'est pas requise. Celui-ci est plus simple que la réponse de @ nik.
Halil
xxx représente le remplacement? dans le cas de l'affiche originale, cela aurait été "jkl"
Awinad
1
La solution la plus propre de toutes.
MT Head
3
Peut-être souligner plus clairement qu'il s'agit d'une extension Bash qui n'existe pas dans POSIX shou généralement dans d'autres shells.
tripleee
Doux - il y a tellement de coins obscurs dans le monde Unix - jamais vu celui-ci auparavant.
wcochran
40

Utilisation de mmv :

mmv "fgh*" "jkl#1"
Lee Netherton
la source
1
Wow, c'est une excellente et simple façon de résoudre le problème! Heureux d'être présenté à mmv, merci!
Hendeca
1
Merci!!! Je n'avais jamais entendu parler de mmv auparavant. Je viens de l'installer et de jouer avec - simple, puissant.
Nick Rice du
3
si vous souhaitez renommer par lots, utilisez récursivement ;en conjonction avec #1. exemple:mmv ";fgh*" "#1jkl#2"
Alp
Solution très élégante!
Le petit étudiant de Fermat
1
Exactement ce dont j'avais besoin. Capturez des groupes avec ' ' et rappelez-les avec # 1, # 2, ... EX: mmv "my show ep 1080p. *" "My.show. # 1. # 2" = my.show.001.avi
Lundy
20

Il existe de nombreuses façons de le faire (toutes ne fonctionneront pas sur tous les systèmes Unixy):

  • ls | cut -c4- | xargs -I§ mv fgh§ jkl§

    Le § peut être remplacé par tout ce qui vous convient. Vous pouvez le faire find -execaussi mais cela se comporte subtilement différemment sur de nombreux systèmes, donc j'évite généralement cela

  • for f in fgh*; do mv "$f" "${f/fgh/jkl}";done

    Brut mais efficace comme on dit

  • rename 's/^fgh/jkl/' fgh*

    Vraiment joli, mais le renommage n'est pas présent sur BSD, qui est le système afix le plus courant.

  • rename fgh jkl fgh*

  • ls | perl -ne 'chomp; next unless -e; $o = $_; s/fgh/jkl/; next if -e; rename $o, $_';

    Si vous insistez pour utiliser Perl, mais qu'il n'y a pas de renommage sur votre système, vous pouvez utiliser ce monstre.

Certains d'entre eux sont un peu compliqués et la liste est loin d'être complète, mais vous trouverez ici ce que vous voulez pour à peu près tous les systèmes Unix.

iwein
la source
2
j'aime ce genre de monstre!
lefakir
ouais, j'ai aussi un point faible pour perl :)
iwein
Le monstre Perl ici fonctionne parfaitement si vous avez des espaces dans vos noms de fichiers.
mlerley
13
rename fgh jkl fgh*
Judy2K
la source
6
Sur ma machine, cela produit l'erreur "Bareword" fgh "non autorisé alors que" strict subs "est utilisé à (eval 1) ligne 1."
Stephan202
@ Stephan202, quelle est votre machine?
nik
Ubuntu 8.10 (perl v5.10.0 / 2009-06-26)
Stephan202
@ Stephan202 J'ai le même problème, avez-vous résolu? (7 ans plus tard)
whossname
1
@whossname: désolé, je ne m'en souviens vraiment pas. (Réponse lente en raison des vacances.)
Stephan202
8

Utilisation find, xargset sed:

find . -name "fgh*" -type f -print0 | xargs -0 -I {} sh -c 'mv "{}" "$(dirname "{}")/`echo $(basename "{}") | sed 's/^fgh/jkl/g'`"'

C'est plus complexe que la solution de @ nik mais cela permet de renommer les fichiers récursivement. Par exemple, la structure,

.
├── fghdir
│   ├── fdhfilea
│   └── fghfilea
├── fghfile\ e
├── fghfilea
├── fghfileb
├── fghfilec
└── other
    ├── fghfile\ e
    ├── fghfilea
    ├── fghfileb
    └── fghfilec

serait transformé en cela,

.
├── fghdir
│   ├── fdhfilea
│   └── jklfilea
├── jklfile\ e
├── jklfilea
├── jklfileb
├── jklfilec
└── other
    ├── jklfile\ e
    ├── jklfilea
    ├── jklfileb
    └── jklfilec

La clé pour le faire fonctionner xargsest d' invoquer le shell à partir de xargs .

luissquall
la source
1
J'allais poster quelque chose comme ça, mais il m'aurait fallu une heure pour obtenir la bonne commande
Juan Mendes
3

Pour installer le script de renommage Perl :

sudo cpan install File::Rename

Il y a deux renoms comme mentionné dans les commentaires dans la réponse de Stephan202. Les distributions basées sur Debian ont renommé Perl . Les distributions Redhat / rpm ont le C renommé .
OS X n'en a pas installé par défaut (au moins en 10.8), ni Windows / Cygwin.

Sam Inverso
la source
3

Voici une façon de le faire en utilisant la ligne de commande Groovy:

groovy -e 'new File(".").eachFileMatch(~/fgh.*/) {it.renameTo(it.name.replaceFirst("fgh", "jkl"))}'
jesseplymale
la source
2

Sur Solaris, vous pouvez essayer:

for file in `find ./ -name "*TextForRename*"`; do 
    mv -f "$file" "${file/TextForRename/NewText}"
done
Andrei Aleksandrov
la source
Vous obtenez un demi-point pour citer les noms de fichiers dans la boucle, mais for file in $(find)est fondamentalement défectueux et ne peut pas être corrigé avec des guillemets. Si le findrendement , ./file name with spacesvous obtiendrez une forboucle sur ./file, name, withet spaceset aucune quantité de cité dans la boucle contribuera (ou même nécessaire).
tripleee
2
#!/bin/sh

#replace all files ended witn .f77 to .f90 in a directory

for filename in *.f77
do 
    #echo $filename
    #b= echo $filename | cut -d. -f1
    #echo $b    
    mv "${filename}" "${filename%.f77}.f90"    
done
Suragini
la source
2

Ce script a fonctionné pour moi pour le renommage récursif avec des répertoires / noms de fichiers contenant éventuellement des espaces blancs:

find . -type f -name "*\;*" | while read fname; do
    dirname=`dirname "$fname"`
    filename=`basename "$fname"`
    newname=`echo "$filename" | sed -e "s/;/ /g"`
    mv "${dirname}/$filename" "${dirname}/$newname"
done

Remarquez l' sedexpression qui, dans cet exemple, remplace toutes les occurrences de ;with space . Celui-ci doit bien entendu être remplacé en fonction des besoins spécifiques.

nielsen
la source
Merci beaucoup ! Great script
Walter Schrabmair
1

Utilisation des outils StringSolver (Windows & Linux bash) qui traitent par des exemples:

filter fghfilea ok fghreport ok notfghfile notok; mv --all --filter fghfilea jklfilea

Il calcule d' abord un filtre basé sur des exemples , où l'entrée est le nom des fichiers et la sortie (ok et notok, chaînes arbitraires). Si le filtre avait l'option --auto ou était appelé seul après cette commande, il créerait respectivement un dossier oket un dossier notoket leur enverrait des fichiers.

Ensuite, en utilisant le filtre, la mvcommande est un mouvement semi-automatique qui devient automatique avec le modificateur --auto. En utilisant le filtre précédent grâce à --filter, il trouve un mappage de fghfileaà jklfileapuis l'applique sur tous les fichiers filtrés.


Autres solutions unifilaires

Autres façons équivalentes de faire la même chose (chaque ligne est équivalente), vous pouvez donc choisir votre façon préférée de le faire.

filter fghfilea ok fghreport ok notfghfile notok; mv --filter fghfilea jklfilea; mv
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter fghfilea "mv fghfilea jklfilea"
# Even better, automatically infers the file name
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter "mv fghfilea jklfilea"

Solution en plusieurs étapes

Pour rechercher soigneusement si les commandes fonctionnent correctement, vous pouvez taper ce qui suit:

filter fghfilea ok
filter fghfileb ok
filter fghfileb notok

et lorsque vous êtes certain que le filtre est bon, effectuez le premier mouvement:

mv fghfilea jklfilea

Si vous souhaitez tester et utiliser le filtre précédent, tapez:

mv --test --filter

Si la transformation n'est pas celle que vous vouliez (par exemple, même si mv --explainvous voyez que quelque chose ne va pas), vous pouvez taper mv --clearpour redémarrer le déplacement des fichiers, ou ajouter d'autres exemples mv input1 input2où input1 et input2 sont d'autres exemples

Lorsque vous êtes confiant, tapez simplement

mv --filter

et voilà! Tout le changement de nom se fait à l'aide du filtre.

AVERTISSEMENT: Je suis co-auteur de ce travail réalisé à des fins académiques. Il pourrait également y avoir bientôt une fonctionnalité de production de bash.

Mikaël Mayer
la source
1

Il était beaucoup plus facile (sur mon Mac) de le faire dans Ruby. Voici 2 exemples:

# for your fgh example. renames all files from "fgh..." to "jkl..."
files = Dir['fgh*']

files.each do |f|
  f2 = f.gsub('fgh', 'jkl')
  system("mv #{f} #{f2}")
end

# renames all files in directory from "021roman.rb" to "021_roman.rb"
files = Dir['*rb'].select {|f| f =~ /^[0-9]{3}[a-zA-Z]+/}

files.each do |f|
  f1 = f.clone
  f2 = f.insert(3, '_')
  system("mv #{f1} #{f2}")
end
Raymond Gan
la source
1

Utilisation de renamer :

$ renamer --find /^fgh/ --replace jkl * --dry-run

Retirez le --dry-rundrapeau une fois que vous êtes satisfait que la sortie semble correcte.

Lloyd
la source
1

Ma version de renommer les fichiers de masse:

for i in *; do
    echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh
mdev
la source
J'aime pouvoir vérifier avant d'exécuter le script
ardochhigh
Cela rompt magnifiquement sur les noms de fichiers contenant des espaces, des guillemets ou d'autres métacaractères shell.
tripleee
1

Je recommanderais d'utiliser mon propre script, qui résout ce problème. Il a également des options pour changer l'encodage des noms de fichiers et pour convertir la combinaison de signes diacritiques en caractères précomposés, un problème que j'ai toujours lorsque je copie des fichiers depuis mon Mac.

#!/usr/bin/perl

# Copyright (c) 2014 André von Kugland

# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

$help_msg =
"rename.pl, a script to rename files in batches, using Perl
           expressions to transform their names.
Usage:
    rename.pl [options] FILE1 [FILE2 ...]
Where options can be:
    -v                      Verbose.
    -vv                     Very verbose.
    --apply                 Really apply modifications.
    -e PERLCODE             Execute PERLCODE. (e.g. 's/a/b/g')
    --from-charset=CS       Source charset. (e.g. \"iso-8859-1\")
    --to-charset=CS         Destination charset. (e.g. \"utf-8\")
    --unicode-normalize=NF  Unicode normalization form. (e.g. \"KD\")
    --basename              Modifies only the last element of the path.
";

use Encode;
use Getopt::Long;
use Unicode::Normalize 'normalize';
use File::Basename;
use I18N::Langinfo qw(langinfo CODESET);

Getopt::Long::Configure ("bundling");

# ----------------------------------------------------------------------------------------------- #
#                                           Our variables.                                        #
# ----------------------------------------------------------------------------------------------- #

my $apply = 0;
my $verbose = 0;
my $help = 0;
my $debug = 0;
my $basename = 0;
my $unicode_normalize = "";
my @scripts;
my $from_charset = "";
my $to_charset = "";
my $codeset = "";

# ----------------------------------------------------------------------------------------------- #
#                                        Get cmdline options.                                     #
# ----------------------------------------------------------------------------------------------- #

$result = GetOptions ("apply" => \$apply,
                      "verbose|v+" => \$verbose,
                      "execute|e=s" => \@scripts,
                      "from-charset=s" => \$from_charset,
                      "to-charset=s" => \$to_charset,
                      "unicode-normalize=s" => \$unicode_normalize,
                      "basename" => \$basename,
                      "help|h|?" => \$help,
                      "debug" => \$debug);

# If not going to apply, then be verbose.
if (!$apply && $verbose == 0) {
  $verbose = 1;
}

if ((($#scripts == -1)
  && (($from_charset eq "") || ($to_charset eq ""))
  && $unicode_normalize eq "")
  || ($#ARGV == -1) || ($help)) {
  print $help_msg;
  exit(0);
}

if (($to_charset ne "" && $from_charset eq "")
  ||($from_charset eq "" && $to_charset ne "")
  ||($to_charset eq "" && $from_charset eq "" && $unicode_normalize ne "")) {
  $codeset = langinfo(CODESET);
  $to_charset = $codeset if $from_charset ne "" && $to_charset eq "";
  $from_charset = $codeset if $from_charset eq "" && $to_charset ne "";
}

# ----------------------------------------------------------------------------------------------- #
#         Composes the filter function using the @scripts array and possibly other options.       #
# ----------------------------------------------------------------------------------------------- #

$f = "sub filterfunc() {\n    my \$s = shift;\n";
$f .= "    my \$d = dirname(\$s);\n    my \$s = basename(\$s);\n" if ($basename != 0);
$f .= "    for (\$s) {\n";
$f .= "        $_;\n" foreach (@scripts);   # Get scripts from '-e' opt. #
# Handle charset translation and normalization.
if (($from_charset ne "") && ($to_charset ne "")) {
  if ($unicode_normalize eq "") {
    $f .= "        \$_ = encode(\"$to_charset\", decode(\"$from_charset\", \$_));\n";
  } else {
    $f .= "        \$_ = encode(\"$to_charset\", normalize(\"$unicode_normalize\", decode(\"$from_charset\", \$_)));\n"
  }
} elsif (($from_charset ne "") || ($to_charset ne "")) {
    die "You can't use `from-charset' nor `to-charset' alone";
} elsif ($unicode_normalize ne "") {
  $f .= "        \$_ = encode(\"$codeset\", normalize(\"$unicode_normalize\", decode(\"$codeset\", \$_)));\n"
}
$f .= "    }\n";
$f .= "    \$s = \$d . '/' . \$s;\n" if ($basename != 0);
$f .= "    return \$s;\n}\n";
print "Generated function:\n\n$f" if ($debug);

# ----------------------------------------------------------------------------------------------- #
#                 Evaluates the filter function body, so to define it in our scope.               #
# ----------------------------------------------------------------------------------------------- #

eval $f;

# ----------------------------------------------------------------------------------------------- #
#                  Main loop, which passes names through filters and renames files.               #
# ----------------------------------------------------------------------------------------------- #

foreach (@ARGV) {
  $old_name = $_;
  $new_name = filterfunc($_);

  if ($old_name ne $new_name) {
    if (!$apply or (rename $old_name, $new_name)) {
      print "`$old_name' => `$new_name'\n" if ($verbose);
    } else {
      print "Cannot rename `$old_name' to `$new_name'.\n";
    }
  } else {
    print "`$old_name' unchanged.\n" if ($verbose > 1);
  }
}
André von Kugland
la source
2
Notez que les réponses de lien uniquement sont déconseillées, les réponses SO devraient être le point final d'une recherche de solution (par rapport à une autre étape de références, qui ont tendance à devenir obsolète au fil du temps). Veuillez envisager d'ajouter un synopsis autonome ici, en conservant le lien comme référence.
kleopatra
Comme prédit par @kleopatra , le lien a été obtenustale over time
y2k-shubham
1
@ y2k-shubham, plus maintenant.
André von Kugland
0

Cela a fonctionné pour moi en utilisant regexp:

Je voulais que les fichiers soient renommés comme ceci:

file0001.txt -> 1.txt
ofile0002.txt -> 2.txt 
f_i_l_e0003.txt -> 3.txt

en utilisant le [az | _] + 0 * ([0-9] +. ) regexp où ([0-9] +. ) est une sous-chaîne de groupe à utiliser dans la commande rename

ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)/, arr) { print   arr[0]  " "  arr[1] }'|xargs  -l mv

Produit:

mv file0001.txt 1.txt
mv ofile0002.txt 2.txt
mv f_i_l_e0003.txt 3.txt

Un autre exemple:

file001abc.txt -> abc1.txt
ofile0002abcd.txt -> abcd2.txt 

ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)([a-z]+)/, arr) { print   arr[0]  " "  arr[2] arr[1] }'|xargs  -l mv

Produit:

  mv file001abc.txt abc1.txt
  mv ofile0002abcd.txt abcd2.txt 

Attention, soyez prudent.

Steven Lizarazo
la source
0

J'ai écrit ce script pour rechercher tous les fichiers .mkv en renommant récursivement les fichiers trouvés en .avi. Vous pouvez le personnaliser selon vos besoins. J'ai ajouté d'autres choses telles que l'obtention du répertoire de fichiers, de l'extension, du nom de fichier à partir d'un chemin de fichier au cas où vous devriez vous référer à quelque chose à l'avenir.

find . -type f -name "*.mkv" | while read fp; do 
fd=$(dirname "${fp}");
fn=$(basename "${fp}");
ext="${fn##*.}";
f="${fn%.*}";
new_fp="${fd}/${f}.avi"
mv -v "$fp" "$new_fp" 
done;
David Okwii
la source
0

Un script générique pour exécuter une sedexpression sur une liste de fichiers (combine la sedsolution avec la renamesolution ):

#!/bin/sh

e=$1
shift

for f in $*; do
    fNew=$(echo "$f" | sed "$e")
    mv "$f" "$fNew";
done

Appelez en passant le script une sedexpression, puis n'importe quelle liste de fichiers, comme une version de rename:

script.sh 's/^fgh/jkl/' fgh*
palswim
la source
0

Vous pouvez également utiliser le script ci-dessous. il est très facile à exécuter sur le terminal ...

// Renommer plusieurs fichiers à la fois

for file in  FILE_NAME*
do
    mv -i "${file}" "${file/FILE_NAME/RENAMED_FILE_NAME}"
done

Exemple:-

for file in  hello*
do
    mv -i "${file}" "${file/hello/JAISHREE}"
done
Sanjay Singh
la source
0

Une autre extension de paramètre possible :

for f in fgh*; do mv -- "$f" "jkl${f:3}"; done
PesaThe
la source