Cambiar el nombre de varios archivos según el patrón en Unix


248

Hay varios archivos en un directorio que comienzan con un prefijo fgh, por ejemplo:

fghfilea
fghfileb
fghfilec

Quiero cambiarles el nombre a todos para comenzar con el prefijo jkl. ¿Hay un solo comando para hacer eso en lugar de cambiar el nombre de cada archivo individualmente?



Respuestas:


289

Hay varias formas, pero usar renameprobablemente sea la más fácil.

Usando una versión de rename:

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

Usando otra versión de rename(igual que la respuesta de Judy2K ):

rename fgh jkl fgh*

Debe consultar la página de manual de su plataforma para ver cuál de los anteriores se aplica.


77
+1 Ni siquiera sabía sobre cambiar el nombre ... Ahora puedo dejar de usar un bucle for con mv y sed ... ¡Gracias!
balpha

2
Se está vinculando a otro diferente, renameentonces está mostrando la sintaxis para unixhelp.ed.ac.uk/CGI/man-cgi?rename es el otro
Hasturkun

77
No está presente en todos los sistemas * nix. No en Max OS X para uno, y no hay paquete en fink para obtenerlo. No he mirado a MacPorts.
dmckee --- ex-gatito moderador

66
AFAICT, renameparece ser un script o utilidad específica de Linux. Si le importa la portabilidad, continúe usando sedbucles y o un script Perl en línea.
D.Shawley

33
brew install renameen OS X :)
sam

117

Esta es la forma sedy mvse puede utilizar en conjunto para hacer cambio de nombre:

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

Según el comentario a continuación, si los nombres de los archivos tienen espacios, las comillas pueden necesitar rodear la subfunción que devuelve el nombre para mover los archivos a:

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

1
Muy cerca. Tenga en cuenta que solo desea hacer coincidir la primera aparición de fgh: 's / ^ fgh / jkl / g' (El cursor marca la diferencia).
Stephan202

2
Solo por razones de precisión ... Quieres decir "fgh al principio del nombre", no "la primera aparición de fgh". / ^ fgh / coincidirá con "fghi", pero no con "efgh".
Dave Sherohman el

@Stephan, eso fue un error tipográfico de mi parte (lo arregló).
nik

55
Si no tiene acceso a "cambiar el nombre", esto funciona muy bien. El código puede requerir comillas si los nombres de sus archivos incluyen espacios. for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
Dave Nelson el

1
@nik sin comillas cambiar el nombre de esta lista de archivos se generará un error: touch fghfilea fghfileb fghfilec fghfile\ d. Le sugiero que tenga en cuenta los comentarios de @DaveNelson.
luissquall

75

El cambio de nombre podría no estar en todos los sistemas. así que si no lo tienes, usa el shell de este ejemplo en bash shell

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

1
En esta solución, sedno se requiere comando. Esta es más simple que la respuesta de @nik.
Halil

xxx representa el reemplazo? en el caso del póster original, eso habría sido "jkl"
Awinad

1
La solución más limpia de todas.
MT Head

3
Quizás señale más enfáticamente que esta es una extensión Bash que no existe en POSIX sho en general en otros shells.
tripleee

Dulce, hay tantos rincones oscuros en el mundo de Unix, nunca antes había visto este.
wcochran

40

Usando mmv :

mmv "fgh*" "jkl#1"

1
¡Guau, esta es una manera excelente y simple de resolver el problema! Me alegro de ser presentado a mmv, gracias!
Hendeca

1
¡¡¡Gracias!!! Nunca había oído hablar de mmv antes. Acabo de instalarlo y he estado jugando con él: simple, potente.
Nick Rice

3
si desea cambiar el nombre por lotes, use recursivamente ;junto con #1. ejemplo:mmv ";fgh*" "#1jkl#2"
Alp

Solución muy elegante!
Fermat's Little Student

1
Exactamente lo que necesitaba. Capture grupos con ' ' y vuelva a llamarlos con # 1, # 2, ... EJ: mmv "my show ep 1080p. *" "My.show. # 1. # 2" = my.show.001.avi
Lundy

20

Hay muchas formas de hacerlo (no todas funcionarán en todos los sistemas unixy):

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

    El § puede ser reemplazado por cualquier cosa que le resulte conveniente. También podría hacer esto, find -execpero eso se comporta sutilmente diferente en muchos sistemas, por lo que generalmente evito eso

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

    Crudo pero efectivo como dicen

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

    Realmente bonito, pero el cambio de nombre no está presente en BSD, que es el sistema unix más común afaik.

  • rename fgh jkl fgh*

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

    Si insiste en usar Perl, pero no hay cambio de nombre en su sistema, puede usar este monstruo.

Algunos de ellos son un poco complicados y la lista está lejos de ser completa, pero aquí encontrará lo que desea para casi todos los sistemas Unix.


2
Me encanta este tipo de monstruo!
lefakir

Sí, todavía tengo un punto débil para Perl :)
iwein


El monstruo de Perl aquí funciona perfectamente si tienes espacios en tus nombres de archivo.
mlerley

13
rename fgh jkl fgh*

66
En mi máquina, esto produce el error 'Bareword "fgh" no permitido mientras que "subs estrictos" en uso en (eval 1) línea 1.'
Stephan202

@ Stephan202, ¿cuál es su máquina?
nik

Ubuntu 8.10 (perl v5.10.0 / 2009-06-26)
Stephan202

@ Stephan202 Tengo el mismo problema, ¿resolvió? (7 años después)
whossname

1
@ whossname: lo siento, realmente no puedo recordar. (Respuesta lenta debido a vacaciones.)
Stephan202

8

Utilizando find, xargsy sed:

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

Es más complejo que la solución de @ nik, pero permite cambiar el nombre de los archivos de forma recursiva. Por ejemplo, la estructura,

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

se transformaría a esto,

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

La clave para que funcione xargses invocar el shell desde xargs .


1
Iba a publicar algo como esto, pero me habría llevado una hora obtener el comando correcto
Juan Mendes


3

Aquí hay una manera de hacerlo usando la línea de comandos Groovy:

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

2

En Solaris puedes probar:

for file in `find ./ -name "*TextForRename*"`; do 
    mv -f "$file" "${file/TextForRename/NewText}"
done

Obtiene medio punto para citar los nombres de archivo dentro del bucle, pero for file in $(find)es fundamentalmente defectuoso y no se puede corregir con una cita. Si findvuelve ./file name with spacesobtendrá un forbucle sobre ./file, name, with, y spacesy ninguna cantidad de citar dentro del bucle ayudará (o incluso ser necesario).
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

2

Este script funcionó para mí para renombrar recursivamente con directorios / nombres de archivo que posiblemente contienen espacios en blanco:

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

Observe la sedexpresión que en este ejemplo reemplaza todas las ocurrencias de ;con espacio . Esto, por supuesto, debe reemplazarse de acuerdo con las necesidades específicas.


Muchas gracias ! Gran guión
Walter Schrabmair

1

Usando herramientas StringSolver (Windows y Linux bash) que procesan con ejemplos:

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

Primero calcula un filtro basado en ejemplos , donde la entrada son los nombres de archivo y la salida (ok y notok, cadenas arbitrarias). Si filter tuviera la opción --auto o se invocara solo después de este comando, crearía una carpeta oky una carpeta notoky enviaría los archivos respectivamente.

Luego, usando el filtro, el mvcomando es un movimiento semiautomático que se vuelve automático con el modificador --auto. El uso de los filtros anteriores gracias a --filter, que encuentra una correspondencia entre fghfileaa jklfileay luego se aplica en todos los archivos filtrados.


Otras soluciones de una línea

Otras formas equivalentes de hacer lo mismo (cada línea es equivalente), para que pueda elegir su forma favorita de hacerlo.

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"

Solución de múltiples pasos

Para encontrar cuidadosamente si los comandos funcionan bien, puede escribir lo siguiente:

filter fghfilea ok
filter fghfileb ok
filter fghfileb notok

y cuando esté seguro de que el filtro es bueno, realice el primer movimiento:

mv fghfilea jklfilea

Si desea probar y usar el filtro anterior, escriba:

mv --test --filter

Si la transformación no es lo que deseaba (por ejemplo, incluso mv --explaincuando ve que algo está mal), puede escribir mv --clearpara reiniciar el movimiento de archivos o agregar más ejemplos mv input1 input2donde input1 y input2 son otros ejemplos

Cuando tenga confianza, solo escriba

mv --filter

¡y voilá! Todo el cambio de nombre se realiza utilizando el filtro.

DESCARGO DE RESPONSABILIDAD: Soy coautor de este trabajo realizado con fines académicos. También podría haber una característica de producción de bash pronto.


1

Fue mucho más fácil (en mi Mac) hacer esto en Ruby. Aquí hay 2 ejemplos:

# 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

1

Usando renamer :

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

Elimine la --dry-runbandera una vez que esté satisfecho, la salida se ve correcta.


1

Mi versión de renombrar archivos masivos:

for i in *; do
    echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh

Me gusta la capacidad de verificar antes de ejecutar la secuencia de comandos
ardochhigh

Esto se rompe magníficamente en los nombres de archivos que contienen espacios, comillas u otros metacaracteres de shell.
tripleee

1

Recomendaría usar mi propio script, que resuelve este problema. También tiene opciones para cambiar la codificación de los nombres de los archivos y convertir la combinación de diacríticos en caracteres precompuestos, un problema que siempre tengo cuando copio archivos de mi 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);
  }
}

2
Tenga en cuenta que se desaconsejan las respuestas de solo enlace , las respuestas SO deben ser el punto final de una búsqueda de una solución (frente a otra escala de referencias, que tienden a quedarse obsoletas con el tiempo). Considere agregar una sinopsis independiente aquí, manteniendo el enlace como referencia.
kleopatra el

Según lo predicho por @kleopatra , el enlace se ha vueltostale over time
y2k-shubham

1
@ y2k-shubham, ya no.
André von Kugland

0

Esto funcionó para mí usando regexp:

Quería que los archivos se renombraran así:

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

use la expresión regular [az | _] + 0 * ([0-9] +. ) donde ([0-9] +. ) es una subcadena de grupo para usar en el comando de cambio de nombre

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

Produce:

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

Otro ejemplo:

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

Produce:

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

Advertencia, ten cuidado.


0

Escribí este script para buscar todos los archivos .mkv renombrando recursivamente los archivos encontrados a .avi. Puede personalizarlo según sus necesidades. He agregado algunas otras cosas, como obtener el directorio de archivos, la extensión, el nombre de archivo de una ruta de archivo en caso de que necesite referirse a algo en el futuro.

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;

0

Un script genérico para ejecutar una sedexpresión en una lista de archivos (combina la sedsolución con la renamesolución ):

#!/bin/sh

e=$1
shift

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

Invoque pasando una sedexpresión al script y luego cualquier lista de archivos, como una versión de rename:

script.sh 's/^fgh/jkl/' fgh*

0

También puedes usar el siguiente script. es muy fácil de ejecutar en la terminal ...

// Cambiar el nombre de varios archivos a la vez

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

Ejemplo:-

for file in  hello*
do
    mv -i "${file}" "${file/hello/JAISHREE}"
done

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.