Mover arquivos de subdiretórios para um único diretório e prefixar o nome do diretório original

3

Eu tenho uma estrutura de diretórios como esta:

./a/1.png
./a/2.png
./a/3.png
./b/1.png
./b/2.png
./b/3.png
./c/1.png
...

E eu quero pegar todos os arquivos nos subdiretórios e movê-los para um novo diretório para que seus nomes sejam parecidos com

../dest/a_1.png
../dest/a_2.png
../dest/a_3.png
../dest/b_1.png
../dest/b_2.png
../dest/b_3.png
../dest/c_1.png
...

O mais próximo que consegui encontrar sem escrever um script para fazer isso arquivo por arquivo é usar find com a opção --backup=numbered que condensaria meus arquivos em um único diretório, mas acabaria perdendo o diretório contexto do nome do arquivo.

Existe uma maneira sucinta de realizar isso?

    
por Brad Dwyer 21.08.2017 / 18:33

4 respostas

8

Com o comando rename autônomo do Perl:

rename -n 's|/|_|; s|^|dest/|' */*.png

Saída:

a/1.png renamed as dest/a_1.png
a/2.png renamed as dest/a_2.png
a/3.png renamed as dest/a_3.png
b/1.png renamed as dest/b_1.png
b/2.png renamed as dest/b_2.png
b/3.png renamed as dest/b_3.png
c/1.png renamed as dest/c_1.png

Se tudo estiver correto, remova a opção -n .

    
por 21.08.2017 / 21:30
6

Com Bash 4 e globalização recursiva ( shopt -s globstar ):

for f in **/*.png; do
  dn=$(basename "$(dirname "$f")")
  bn=$(basename "$f")
  mv -- "$f" "../dest/${dn}_${bn}"
done

Observe que dirname /foo/bar/baz retorna /foo/bar e não apenas bar , e é por isso que você precisa chamar basename no resultado para obter apenas bar caso esteja trabalhando em outra pasta pai.

    
por 21.08.2017 / 18:45
3

Como uma abordagem alternativa, você não precisa de programas externos como rename , basename , etc - todos podem ser manipulados dentro da expansão do parâmetro bash : -

find SourceDir ... | while read -r f; do mv "$f" "TargetDir/${f//\//_}"; done

A expansão é um pouco difícil de seguir, então aqui está o que acontece: -

  • Os arquivos a serem movidos são encontrados com find .
  • Cada nome de arquivo é lido, por sua vez, em $f .
  • O parâmetro read -r e as aspas duplas em torno das expansões manipulam nomes estranhos, incluindo espaços nos nomes dos arquivos.
  • O comando mv move nomes como a/b/c para TargetDir/a_b_c .
  • A expansão de destino substitui todos os / por _ , mas parece assustadora porque / faz parte da sintaxe de substituição.
  • A forma geral é ${param/old/new} , que substitui a primeira instância de old por new na expansão.
  • O formulário necessário aqui é ${param//old/new} , que substitui todas as instâncias de old por new na expansão.
  • Para que / faça parte da sequência antiga, ela deve ter escape como \/ , portanto, o obscuro ${f//\//_} : o primeiro / apresenta a sintaxe de substituição, a segunda / especifica substitua todos os , o terceiro (com escape) / é a sequência antiga e o% final / apresenta a nova sequência ( _ ).

Eu não vejo frequentemente esta forma de expansão em scripts, mas vale a pena conhecer, pois pode ser muito útil às vezes.

Existem alguns nomes de arquivos que quebram isso (caracteres de nova linha incorporados e espaços iniciais ou finais, embora haja duas maneiras em torno do último: -

  • Use read -r em vez de read -r f e use REPLY em vez de f .
  • Use while f="$(line)" em vez de read -r f .

O último é mais puro, mas usa o programa externo line , que pode não estar disponível em todos os sistemas, embora possa ser codificado como uma função:

line() { read -r; r=$?; echo "$REPLY"; return $r; }
    
por 22.08.2017 / 00:58
2

Como alternativa a renomear , há mmv , que usa padrões de shell padrão.

Da página do manual:

Mmv moves (or copies, appends, or links, as specified) each source file matching a from pattern to the target name specified by the to pattern. This multiple action is performed safely, i.e. without any unexpected deletion of files due to collisions of target names with existing filenames or with other target names. Furthermore, before doing anything, mmv attempts to detect any errors that would result from the entire set of actions specified and gives the user the choice of either proceeding by avoiding the offending parts or aborting.

Para o seu caso de uso:

mmv -n '*/*' 'dest/#1_#2'
a/1.jpg -> dest/a_1.jpg
a/2.jpg -> dest/a_2.jpg
a/3.jpg -> dest/a_3.jpg
b/1.jpg -> dest/b_1.jpg
b/2.jpg -> dest/b_2.jpg
b/3.jpg -> dest/b_3.jpg
c/1.jpg -> dest/c_1.jpg
c/2.jpg -> dest/c_2.jpg
c/3.jpg -> dest/c_3.jpg

Como renomear , '-n' não é executado. Remova-o para renomear de verdade.

Além de renomear, o mmv também suporta copiar, vincular ou anexar arquivos.

    
por 22.08.2017 / 14:36