Existem várias maneiras de abordar essa questão. Por favor, leia atentamente as instruções para obter melhores resultados.
Nesta resposta:
- abordagem Python
-
find
+bash
approach - abordagem somente do bash com globstar
1. Solução Python
O Python é uma linguagem bastante poderosa para a administração do sistema, e a passagem da árvore de diretórios pode ser feita através da função os.walk()
. No script apresentado abaixo, estamos fazendo exatamente isso - estamos localizando todos os arquivos e operando em cada um deles, determinando o caminho completo para cada arquivo, a extensão do arquivo e renomeando-o via função os.rename()
.
Conteúdo do script
#!/usr/bin/env python3
import os,sys
def get_all_files(treeroot):
for dir,subdirs,files in os.walk(treeroot):
for f in files:
if f in __file__: continue
fullpath = os.path.realpath( os.path.join(dir,f) )
extension = fullpath.split('.')[-1]
newpath = os.path.join(dir, dir + '.' + extension)
print('Move ' + fullpath + ' to ' + newpath )
# os.rename(fullpath,newpath)
def main():
top_dir="."
# If directory not given, assume cwd
if len(sys.argv) == 2: top_dir=sys.argv[1]
get_all_files(top_dir)
if __name__ == '__main__' : main()
NOTA: é muito importante renomear realmente os arquivos que você precisa remover #
antes de # os.rename(fullpath,newpath)
.
Configurando o script
Todas as regras padrão para scripts são aplicadas:
- salve-o como add_location_name.py
no diretório mais alto
- faça executável com chmod +x ./add_location_name.py
- execute com ./add_location_name.py
Testando o script
Aqui está um exemplo de como isso funciona na prática. Eu criei um diretório com outros dois, Movie A (2016)
e Movie B (2016)
. Dentro deles, ambos têm dois arquivos. Nosso script está no mesmo diretório:
$ tree
.
├── add_location_name.py
├── Movie A (2014)
│ ├── filea.mkv
│ └── fileb.srt
└── Movie B (2016)
├── filea.mkv
└── fileb.srt
Então, quando executarmos o script, veremos a seguinte saída:
$ ./add_location_name.py
Move /home/xieerqi/testdir/Movie A (2014)/fileb.srt to ./Movie A (2014)/./Movie A (2014).srt
Move /home/xieerqi/testdir/Movie A (2014)/filea.mkv to ./Movie A (2014)/./Movie A (2014).mkv
Move /home/xieerqi/testdir/Movie B (2016)/fileb.srt to ./Movie B (2016)/./Movie B (2016).srt
Move /home/xieerqi/testdir/Movie B (2016)/filea.mkv to ./Movie B (2016)/./Movie B (2016).mkv
2. Solução via comando de localização e sinalizador -exec
O comando find
é útil de várias maneiras, especialmente ao executar operações em vários níveis da árvore de diretórios. Nesse caso específico, podemos usá-lo para filtrar todos os arquivos e executar uma operação de renomeação neles.
Antes de tudo, deixe-me dar a solução:
find -type f -exec bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";mv "$1" "$fp"/"$fn"."$px"' sh "{}" \;
Parece longo e assustador, certo? Mas não se preocupe, vamos ver como funciona.
Dissecando o comando
Primeiro de tudo, você precisa reconhecer que há duas coisas acontecendo: o comando find
localiza todos os arquivos e bash
part é o que realmente faz a parte de renomeação. Do lado de fora, o que vemos é o simples comando:
find -type f -exec <COMMAND> \;
Isso só encontra todos os arquivos (sem diretórios ou links simbólicos) e chama algum outro comando sempre que encontrar um arquivo. Neste caso, o comando específico que temos é bash
.
Então, o que bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";mv "$1" "$fp"/"$fn"."$px"' sh "{}"
faz? Bem, em primeiro lugar, reconheça a estrutura: bash -c 'command1;command2' arg0 arg1
. Sempre que -c
flag for usado, o primeiro argumento de linha de comando arg0
será definido como $0
, nome do shell, portanto, isso é irrelevante, mas "{}"
é importante. Esse é o espaço reservado entre aspas para o nome do arquivo que find
passará como argumento para bash
.
No interior do comando bash, extraímos o caminho do arquivo fp=$(dirname "$1")
, o nome do diretório fn=$(basename "$fp")
e a extensão ou prefixo do arquivo px="${1##*.}"
. Tudo isso funciona muito bem, já que estamos executando o comando no diretório mais importante (muito importante!). Finalmente, o mv "$1" "$fp"/"$fn"."$px"' sh "{}"
irá renomear o arquivo original que find
nos deu para o novo nome de arquivo, o que construímos com "$fp"/"$fn"."$px"
usando todas essas variáveis.
Exemplo de operação
O teste do comando é realizado no mesmo diretório de antes:
$ tree
.
├── Movie A (2014)
│ ├── filea.mkv
│ └── fileb.srt
└── Movie B (2016)
├── filea.mkv
└── fileb.srt
2 directories, 4 files
E se executarmos o comando, com echo
em vez de mv
, poderemos ver que cada nome de arquivo é renomeado, respectivamente.
$ find -type f -exec bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";echo "$1" "$fp"/"$fn"."$px"' sh ">
./Movie A (2014)/fileb.srt ./Movie A (2014)/Movie A (2014).srt
./Movie A (2014)/filea.mkv ./Movie A (2014)/Movie A (2014).mkv
./Movie B (2016)/fileb.srt ./Movie B (2016)/Movie B (2016).srt
./Movie B (2016)/filea.mkv ./Movie B (2016)/Movie B (2016).mkv
Lembre-se : o comando acima usa echo
apenas para testes. Quando você usa mv
, não há saída, então o comando é silencioso.
3.Aproximação do simulador: Bash e glob star
As duas abordagens acima usam o percurso de árvore recursiva. Como no seu exemplo você tem apenas dois níveis na árvore de diretórios (diretório de filmes e arquivos), podemos simplificar nosso comando shell anterior da seguinte forma:
for f in */* ;do fp=$(dirname "$f"); ext="${f##*.}" ; echo "$f" "$fp"/"$fp"."$ext" ;done
Novamente, a mesma ideia - substitua echo
por mv
quando tiver certeza de que funciona corretamente.
Os resultados do teste são os mesmos:
$ tree
.
├── add_location_name.py
├── Movie A (2014)
│ ├── filea.mkv
│ └── fileb.srt
└── Movie B (2016)
├── filea.mkv
└── fileb.srt
2 directories, 5 files
$ for f in */* ;do fp=$(dirname "$f"); ext="${f##*.}" ; echo "$f" "$fp"/"$fp"."$ext" ;done
Movie A (2014)/filea.mkv Movie A (2014)/Movie A (2014).mkv
Movie A (2014)/fileb.srt Movie A (2014)/Movie A (2014).srt
Movie B (2016)/filea.mkv Movie B (2016)/Movie B (2016).mkv
Movie B (2016)/fileb.srt Movie B (2016)/Movie B (2016).srt