Renomeie vários arquivos com base no padrão no Unix

248

Existem vários arquivos em um diretório que começam com o prefixo fgh, por exemplo:

fghfilea
fghfileb
fghfilec

Eu quero renomear todos eles para começar com o prefixo jkl. Existe um único comando para fazer isso em vez de renomear cada arquivo individualmente?

jww
fonte
Pergunta relacionada: Melhor maneira de renomear arquivos com base em vários padrões .
Michael Le Barbier Grünewald

Respostas:

289

Existem várias maneiras, mas usar rename provavelmente será o mais fácil.

Usando uma versão de rename:

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

Usando outra versão rename(igual à resposta do Judy2K ):

rename fgh jkl fgh*

Você deve verificar a página de manual da sua plataforma para ver qual das opções acima se aplica.

Stephan202
fonte
7
+1 nem sabia sobre renomear ... Agora eu posso parar de usar um loop for com mv e sed ... Obrigado!
balpha
2
Você está ligando para um diferente rename, então você está mostrando sintaxe para unixhelp.ed.ac.uk/CGI/man-cgi?rename é o outro
Hasturkun
7
Não está presente em todos os sistemas * nix. Não no Max OS X para um, e nenhum pacote no fink para obtê-lo. Ainda não olhou para MacPorts.
dmckee --- ex-moderador gatinho
6
AFAICT, renameparece ser um script ou utilitário específico do Linux. Se você se preocupa com a portabilidade, continue usando sede loops ou um script Perl embutido.
21411 DBShawley
33
brew install renameno OS X :)
sam
117

É assim que sede mvpodemos ser usados ​​juntos para renomear:

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

Conforme o comentário abaixo, se os nomes dos arquivos tiverem espaços, as aspas podem precisar envolver a subfunção que retorna o nome para o qual os arquivos são movidos:

for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
nik
fonte
1
Muito perto. Observe que você deseja corresponder apenas à primeira ocorrência de fgh: 's / ^ fgh / jkl / g' (o cursor faz toda a diferença).
21420 Stephanie2
2
Apenas por uma questão de precisão ... Você quer dizer "fgh no começo do nome", não "a primeira ocorrência de fgh". / ^ fgh / corresponderá a "fghi", mas não a "efgh".
Dave Sherohman 06/07/2009
@ Stephan, que foi um erro de digitação da minha parte (corrigi-lo).
Nik
5
Se você não tem acesso para "renomear", isso funciona muito bem. O código pode exigir aspas se os nomes dos arquivos incluírem espaços. for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
Dave Nelson
1
@nik Sem aspas renomear esta lista de ficheiros iria lançar um erro: touch fghfilea fghfileb fghfilec fghfile\ d. Sugiro que leve em consideração as observações do @DaveNelson.
18896 Luisaquall
75

renomear pode não estar em todos os sistemas. Portanto, se você não o tiver, use o shell neste exemplo no bash shell

for f in fgh*; do mv "$f" "${f/fgh/xxx}";done
ghostdog74
fonte
1
Nesta solução, o sedcomando não é necessário. Este é mais simples que a resposta de @ nik.
Halil
xxx significa a substituição? no caso do cartaz original, que teria sido "jkl"
Awinad
1
A solução mais limpa de todas.
MT Head
3
Talvez aponte com mais ênfase que esta é uma extensão do Bash que não existe no POSIX shou geralmente em outros shells.
Tripleee
Doce - existem tantos cantos obscuros no mundo Unix - nunca vi esse antes.
Wcochran
40

Usando mmv :

mmv "fgh*" "jkl#1"
Lee Netherton
fonte
1
Uau, essa é uma maneira excelente e simples de resolver o problema! Fico feliz em ser apresentado ao mmv, obrigado!
Hendeca
1
Obrigado!!! Nunca tinha ouvido falar em mmv antes. Acabei de instalar e jogar com ele - simples, poderoso.
Nick Rice
3
se você deseja renomear em lote, use recursivamente ;em conjunto com #1. exemplo:mmv ";fgh*" "#1jkl#2"
Alp
Solução muito elegante!
Little Student Fermat
1
Exatamente o que eu precisava. Capture grupos com ' ' e ligue-os de volta com # 1, # 2, ... EX: mmv "meu programa ep 1080p. *" "My.show. # 1. # 2" = my.show.001.avi
Lundy
20

Existem várias maneiras de fazer isso (nem todas funcionarão em todos os sistemas unixy):

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

    O § pode ser substituído por qualquer coisa que você achar conveniente. Você poderia fazer isso find -exectambém, mas que se comporta sutilmente diferente em muitos sistemas, então eu costumo evitar isso.

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

    Bruto, mas eficaz, como se costuma dizer

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

    Muito bonito, mas renomear não está presente no BSD, que é o sistema unix mais comum.

  • rename fgh jkl fgh*

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

    Se você insistir em usar Perl, mas não houver renomeação no seu sistema, poderá usar esse monstro.

Alguns deles são um pouco complicados e a lista está longe de estar completa, mas você encontrará o que deseja aqui para praticamente todos os sistemas unix.

iwein
fonte
2
eu amo esse tipo de monstro!
Lefakir
Sim, eu ainda tenho um ponto fraco para perl também :)
Iwein
O monstro Perl aqui funcionará perfeitamente se você tiver espaços em seus nomes de arquivos.
mlerley
13
rename fgh jkl fgh*
Judy2K
fonte
6
Na minha máquina, isso gera o erro 'Bareword "fgh" não permitido enquanto "subs estritos" em uso na (eval 1) linha 1.'
21420 Stephanie2
@ Stephan202, qual é a sua máquina?
nik
Ubuntu 8.10 (perl v5.10.0 / 26-06-2009)
Stephan202
@ Stephan202 Tenho o mesmo problema, você resolveu? (7 anos depois)
whossname 14/08/16
1
@ whossname: desculpe, realmente não consigo lembrar. (Lento responder devido a um feriado.)
Stephan202
8

Usando find, xargse sed:

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

É mais complexo que a solução da @ nik, mas permite renomear arquivos recursivamente. Por exemplo, a estrutura,

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

seria transformado para isso,

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

A chave para fazê-lo funcionar xargsé invocar o shell do xargs .

luissquall
fonte
1
Eu ia postar algo como isso, mas teria me levado uma hora para chegar o comando certo
Juan Mendes
3

Para instalar o script de renomeação do Perl :

sudo cpan install File::Rename

Existem duas renomeações, conforme mencionado nos comentários na resposta de Stephan202. As distribuições baseadas no Debian têm o nome do Perl . Red Hat / rpm distribuições tem a mudança de nome C .
O OS X não possui um instalado por padrão (pelo menos na versão 10.8), nem o Windows / Cygwin.

Sam Inverso
fonte
3

Aqui está uma maneira de fazer isso usando a linha de comando Groovy:

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

No Solaris, você pode tentar:

for file in `find ./ -name "*TextForRename*"`; do 
    mv -f "$file" "${file/TextForRename/NewText}"
done
Andrei Aleksandrov
fonte
Você ganha meio ponto para citar os nomes de arquivo dentro do loop, mas for file in $(find)é fundamentalmente falho e não pode ser corrigido com a citação. Se findretorno ./file name with spacesvocê receberá um forloop sobre ./file, name, withe, spacese nenhuma quantidade de citar dentro do loop vai ajudar (ou até mesmo ser necessário).
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
fonte
2

Esse script funcionou para mim na renomeação recursiva com diretórios / nomes de arquivos possivelmente contendo espaços em branco:

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 a sedexpressão que neste exemplo substitui todas as ocorrências de ;com espaço . Obviamente, isso deve ser substituído de acordo com as necessidades específicas.

Nielsen
fonte
Muito obrigado ! Great script
Walter Schrabmair 21/09/19
1

Usando ferramentas StringSolver (windows e Linux bash), que são processadas por exemplos:

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

Primeiro, ele calcula um filtro com base em exemplos , onde a entrada é o nome do arquivo e a saída (ok e notok, seqüências arbitrárias). Se o filtro tivesse a opção --auto ou fosse invocado sozinho após esse comando, ele criaria uma pasta oke uma pasta notoke enviaria arquivos respectivamente para eles.

Em seguida, usando o filtro, o mvcomando é uma movimentação semi-automática que se torna automática com o modificador --auto. Usando o filtro anterior, graças ao --filter, ele encontra um mapeamento de fghfileapara jklfileae depois o aplica em todos os arquivos filtrados.


Outras soluções de uma linha

Outras maneiras equivalentes de fazer o mesmo (cada linha é equivalente), para que você possa escolher sua maneira favorita de fazê-lo.

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"

Solução em várias etapas

Para descobrir com cuidado se os comandos estão funcionando bem, você pode digitar o seguinte:

filter fghfilea ok
filter fghfileb ok
filter fghfileb notok

e quando tiver certeza de que o filtro está bom, execute o primeiro movimento:

mv fghfilea jklfilea

Se você deseja testar e usar o filtro anterior, digite:

mv --test --filter

Se a transformação não for o que você queria (por exemplo, mesmo que mv --explainvocê veja que algo está errado), digite mv --clearpara reiniciar os arquivos em movimento ou adicione mais exemplos em mv input1 input2que input1 e input2 são outros exemplos

Quando estiver confiante, basta digitar

mv --filter

e voilà! Toda a renomeação é feita usando o filtro.

AVISO LEGAL: Eu sou co-autor deste trabalho feito para fins acadêmicos. Também pode haver um recurso de produção de festança em breve.

Mikaël Mayer
fonte
1

Foi muito mais fácil (no meu Mac) fazer isso no Ruby. Aqui estão 2 exemplos:

# 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
fonte
1

Usando renamer :

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

Remova a --dry-runbandeira quando estiver satisfeito, a saída parece correta.

Lloyd
fonte
1

Minha versão de renomear arquivos em massa:

for i in *; do
    echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh
mdev
fonte
Eu gosto da capacidade de verificar antes de executar o script #
ardochhigh 4/15/15
Isso quebra magnificamente em nomes de arquivos que contêm espaços, aspas ou outros metacaracteres do shell.
Tripleee
1

Eu recomendaria usar meu próprio script, que resolve esse problema. Ele também possui opções para alterar a codificação dos nomes dos arquivos e converter diacríticos combinados em caracteres pré-compostos, um problema que sempre tenho ao copiar arquivos do meu 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
fonte
2
Observe que as respostas somente para links são desencorajadas; as respostas SO devem ser o ponto final de uma pesquisa por uma solução (em comparação com outra parada de referências, que tendem a ficar obsoletas ao longo do tempo). Considere adicionar aqui uma sinopse independente, mantendo o link como referência.
Kleopatra #
Como previsto por @kleopatra , o link chegoustale over time
y2k-shubham 20/01/19
1
@ y2k-shubham, não mais.
André von Kugland 21/01/19
0

Isso funcionou para mim usando o regexp:

Eu queria que os arquivos fossem renomeados assim:

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

use o [az | _] + 0 * ([0-9] +. ) regexp onde ([0-9] +. ) é uma subcadeia de grupo a ser usada no comando rename

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

Produz:

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

Outro exemplo:

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

Produz:

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

Atenção, tenha cuidado.

Steven Lizarazo
fonte
0

Eu escrevi esse script para procurar todos os arquivos .mkv renomeando recursivamente os arquivos encontrados para .avi. Você pode personalizá-lo para seus neeeds. Adicionei algumas outras coisas, como obter diretório, extensão e nome de arquivo a partir de um caminho, caso você precise se referir a algo no 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;
David Okwii
fonte
0

Um script genérico para executar uma sedexpressão em uma lista de arquivos (combina a sedsolução com a renamesolução ):

#!/bin/sh

e=$1
shift

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

Invoque passando uma sedexpressão para o script e, em seguida, qualquer lista de arquivos, como uma versão de rename:

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

Você também pode usar o script abaixo. é muito fácil rodar no terminal ...

// Renomeie vários arquivos por vez

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

Exemplo:-

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

Outra possível expansão de parâmetro :

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