Alfabeta palavras dentro de nomes de arquivos usando sort?

8

Ao ler tutoriais sobre arquivos de renomeação de lote no bash e usar o comando sort para classificar o conteúdo do arquivo, não consegui descobrir como combinar os dois.

Eu tenho um diretório cujo conteúdo é classificado usando tags dentro do nome do arquivo, semelhante a como o programa TagSpaces lida com as coisas. Eu adiciono as tags que eu posso pensar no final do nome do arquivo quando eu criá-lo ou baixá-lo. Aqui está um exemplo:

Sunrise (2) #wallpaper #4k #googleimages.jpg

Agora, quero percorrer todos esses arquivos e renomeá-los para que as tags sejam classificadas em ordem alfabética, sem afetar nada antes ou depois das tags (por exemplo, o título de uma imagem ou a extensão de arquivo). Então, o acima se tornaria:

Sunrise (2) #4k #googleimages #wallpaper.jpg

Como faço isso? Eu não consigo nem descobrir como passar o nome , e não seu conteúdo, para um comando como sort , cuja saída eu poderia então canalizar para mv .

    
por ArdentCertes 06.08.2017 / 14:54

5 respostas

5

#!/bin/bash

for i in dir_for_files/*; do
    filename=${i%%#*}
    sorted_tags=$(grep -o "#[^ .]*" <<< "$i" | sort -n | tr '\n' ' ')
    ext=${i#*.}
    echo mv "$i" "${filename}${sorted_tags% }.$ext"
done

Teste:

##### Before sorting #####    
$ ls -1 dir_for_files
Note (3) #textfile #notes #important.txt
Sunrise (2) #wallpaper #4k #googleimages.jpg
Sunset (2) #wallpaper #2k #images.jpg

$ ./sort_tags.sh

##### After sorting #####
$ ls -1 dir_for_files
Note (3) #important #notes #textfile.txt
Sunrise (2) #4k #googleimages #wallpaper.jpg
Sunset (2) #2k #images #wallpaper.jpg
    
por 07.08.2017 / 00:13
3

Se você tiver o rename ( prename em alguns sistemas) baseado em perl, então poderá fazer um split + classificar as tags usando perl. Por exemplo, dado

$ ls *.jpg
Sunrise (2) #wallpaper #4k #googleimages.jpg

então (com alguma discussão feia para remover e substituir o sufixo .jpg )

$ rename -v 'my @F = split / #/, substr($_,0,-4); $_ = (join " #", shift @F, sort @F) . ".jpg"' *.jpg
Sunrise (2) #wallpaper #4k #googleimages.jpg renamed as Sunrise (2) #4k #googleimages #wallpaper.jpg

Verificando

$ ls *.jpg
Sunrise (2) #4k #googleimages #wallpaper.jpg

Provavelmente muito espaço para melhorias - mas espero que isso lhe dê uma ideia.

    
por 06.08.2017 / 15:31
3

com zsh :

autoload zmv # best in ~/.zshrc
zmv -n '([^#]#)(\#*)(.*)' '$1${(j: :)${(os: :)2}}$3'

(remova o -n (dry-run) se estiver feliz).

  • [^#]# : 0 ou mais caracteres não-# ( # é como * em regexps)
  • s: : dividido no espaço
  • o : order (sort)
  • j: : : junte-se ao espaço.

Então, estamos dividindo a parte entre o primeiro # (incluído) e o último . (excluído) no espaço, classifique a lista resultante à qual nos juntamos de volta com espaço.

Recursivamente:

zmv -n '(**/)([^#]#)(\#*)(.*)' '$1$2${(j: :)${(os: :)3}}$4'

Para permitir espaços nos nomes das tags, podemos dividir em # e recortar espaços à direita, classificar e participar em # com:

zmv -n '([^#]#\#)(*)(.*)' '$1${(j: #:)${(os:#:)2}%% #}$3'

Adicione um qualificador (#qD) glob se você também quiser processar arquivos ocultos ( D ot files) ou quiser processar arquivos em diretórios ocultos.

    
por 06.08.2017 / 21:34
2

Boa pergunta!

Aqui está o meu script bash simples para isso:

for file in *.jpg; do 
    afile=( ${file#*)} ); 
    echo mv "$file" "${file%%#*}$(echo $(sort<(printf "%s\n" "${afile[@]%%.*}"))).jpg";
done

Explicação:

  • Em afile=( ${file#*)} ); : estamos convertendo a string para um array. Nesse estado, o shell está executando a divisão de palavras com espaços, a menos que você cite a string.

    Em ${file#*)} ( recortar-para-primeiro-prefixo ): estamos removendo tudo, desde o início da string até a primeira ) vista usando shell parameter expansion , pois isso resultará em #wallpaper #4k #googleimages.jpg considerando o file="Sunrise (2) #wallpaper #4k #googleimages.jpg"

  • Em ${file%%#*} ( cut-up-last-suffix ); descascando o começo do fim ao começo da corda até o último # visto. isso resultará em Sunrise (2)

  • Em ${afile[@]%%.*} ( recorte-a-último-sufixo ): o mesmo que acima, começando do início ao início da string (aqui em cada array indexado) até último . visto. isso resultará em #wallpaper #4k #googleimages , poderíamos também usar ${afile[@]%.*} better!

  • Em printf "%s\n" "${afile[@]%%.*}" : estamos imprimindo os elementos da matriz com novas linhas ( [@] usado para matriz indexada), (por que com novas linhas? Porque as classificaremos e devemos dividir os elementos em novas linhas)

  • Em $(sort<(printf "%s\n" "${afile[@]%%.*}")) : estamos classificando os elementos (ou tags).

  • Em $(echo $(sort<(printf "%s\n" "${afile[@]%%.*}"))) : o mesmo que acima, mas usamos o comando echo extra para reunir os elementos classificados no único linear.

    possível mesmo com o uso de duplo xargs como ... |xargs -n1| sort | xargs .
    Por favor, veja o exemplo abaixo para entender melhor este passo:

    echo -e "C\n4\nB" |sort
    4
    B
    C
    echo $(echo -e "C\n4\nB" |sort)
    4 B C
    

Finalmente, no final, o comando mv está renomeando o $file para o nome modificado.

Ps: remova echo mv ... na frente do mv para sair do modo de execução e executar renomeação real.

    
por 06.08.2017 / 19:37
1

Isso parece um pouco complicado demais para Bash ser honesto. Não é impossível, mas na minha opinião, você está muito melhor usando uma linguagem feita para programação "real" aqui. Uma solução de Bash provavelmente será inatingível. (Não estou tentando insultar a solução Bash-only, acho que a magia renomear é bem incrível, na verdade)

Dito isso, aqui está uma solução com o Ruby. Você pode escrevê-lo em um arquivo e depois executar o arquivo de dentro do seu shell, já que você tem o Ruby instalado.

#!/usr/bin/env ruby
Dir.glob('*.jpg').each do |filename|
    # 'name' contains the name of the file, without the tags. 'tags' is an array
    # of all tags of the file.
    name, *tags = filename.split(' #')
    # if there aren't any tags, just skip the file.
    if tags.empty?
        next
    end
    # remove the trailing '.jpg' and sort all the tags
    tags.last.gsub!(/\.jpg$/,'')
    tags.sort!
    tags = [name] + tags
    # finally, move the file to the correct location with sorted tags.
    File.rename filename, "#{tags.join(' #')}.jpg"
end

Para executar o script, basta colocá-lo no diretório em que suas imagens estão. Essa solução deve ser bastante resiliente a nomes de imagem e tags bobos, como C# não causará problemas. Dito isso, certifique-se de fazer um backup antes de executar o script. Operações de movimento podem ser tão destrutivas quanto um rm .

    
por 06.08.2017 / 16:01