Ajuda com Regex (script bash)

Oi, moçada, blz?

Tenho uma pasta com um monte de arquivos de backup com nomes num esquema mais ou menos assim “[nome autor] Titulo (detalhes).zip”, onde a parte “Titulo” ainda contém caracteres não convencionais (como ▲, ◆, ☆).

Estive pesquisando sobre formas de renomear estes arquivos massivamente e encontrei em linux - How to bulk-rename files with invalid encoding or bulk-replace invalid encoded characters? - Super User o seguinte script:

#!/usr/bin/env bash
find "$1" -depth -print0 | while IFS= read -r -d '' file; do
  d="$( dirname "$file" )"
  f="$( basename "$file" )"
  new="${f//[^a-zA-Z0-9\/\._\-]/}"
  if [ "$f" != "$new" ]      # if equal, name is already clean, so leave alone
  then
    if [ -e "$d/$new" ]
    then
      echo "Notice: \"$new\" and \"$f\" both exist in "$d":"
      ls -ld "$d/$new" "$d/$f"
    else
      echo mv "$file" "$d/$new"      # remove "echo" to actually rename things
    fi
  fi
done

Funciona fantasticamente bem e eu adaptei, dentro do que eu sei fazer, da seguinte forma:

#!/usr/bin/env bash

find "$1" -depth -print0 | while IFS= read -r -d '' file; do
  d="$( dirname "$file" )"
  f="$( basename "$file" )"
  novo1="${f// /_}"  
  novo2="${novo1//_(*)/}"
  novo3="${novo2//[\\\/\:\*\?\"<>|]/}"
  novo4="${novo3//[^a-zA-Z0-9\/\._\-]/}"
  novo5="${novo4//__/_}"
  new="${novo5//_-_/_}"
  if [ "$f" != "$new" ]      # if equal, name is already clean, so leave alone
  then
    if [ -e "$d/$new" ]
    then
      echo "Notice: \"$new\" and \"$f\" both exist in "$d":"
      ls -ld "$d/$new" "$d/$f"
    else
      echo mv "$file" "$d/$new"      # remove "echo" to actually rename things
    fi
  fi
done

Eu não consegui (por não saber como) usar um único regex para a troca da string do nome do arquivo de uma vez só, então usei várias variáveis em diversos passos. A primeira troca, novo1, pega o nome do arquivo e substitui todos os espaços por underline. Na sequência, novo2, por não precisar da info, eu removi o bloco do final (detalhes), que acaba ficando _(detalhes) depois do passo anterior. Depois, em novo3, removi todos caracteres que dariam problema no Windows e, em novo4, removi os caracteres bizonhos. Alguns arquivos ficaram com dois underlines juntos, ou uma sequência underline-hifen-underline, que foram removidos em novo5 e new.

O código pode ter ficado primário, mas funciona, exceto para um detalhe (tinha que ter):

Alguns arquivos têm no nome números Sub ou Sobre escritos (tipo m3, H2O, etc), que, depois de rodar o script continuaram da mesma forma, sub ou sobre escritos…

Acho que em novo4 a parte do regex que testa se é um número (0-9) deixou passar o caractere sobre/subscrito como se fosse número “normal”…

Tem como ajustar isto? Ou seja, explicitar no código que deve ficar apenas se for um número “normal”? Eu não preciso de nada complexo como passar de m3 para m3, por exemplo. Apenas eliminar o 3 (ou qualquer que seja o caractere) é suficiente. Só não quero caracteres esquisitos nos nomes de arquivos.

Obrigado!!!

1 curtida

Você pode usar o sed com pipeline:

sed 's/ /_/g' | tr -cd '[:alnum:]._-' | 

O que isso faz?

O sed substitui espaços pelo underline

O tr:

d -> deleta o conjunto

c -> inverte o comando (formalmente o nome é complement mas na prática ele só inverte o que é pra deletar)

'[:alnum:].-’ -> é o conjunto de caracteres que nós queremos remover no caso números de 0 a 9,letras de A a Z maiúsculas e minúsculas o ponto final (.) O underline () e o hífen (-)

Então na variável new você poderia colocar assim:

new="$(echo $file | sed 's/ /_/g' | tr -dc '[:alnum:]._-')"

O problema de fazer um RegExp só é que você está fazendo substituições e apagando caracteres de uma vez, o melhor a fazer é usar pipelines (|) porque éo único jeito de ficar legível mas pelo menos fica com uma variável só

No caso:

Natanael#@!#£¥£÷ñáťåňāëł123

Ficaria

Natanael123

(Caracteres sobre/subscrito são removidos também)

Valeu Natanael!

Essa dica do POSIX [:alnum:] resolveu o problema dos sub/sobrescritos. Aprendi bastante também lendo sobre o sed.
Acabei tendo que alterar um pouco o código porque ainda ficaram alguns problemas, como dar fim no " (descrição)" no fim do nome do arquivo e, coisas como “isso & aquilo”, que terminam como “isso__aquilo”, com dois underlines juntos (como tem espaços circundando o caractere especial, isto gera um paradoxo tipo “Tostines” - tira antes, dá problema; tira depois, dá problema também…).

Ficou assim, e testando em várias pastas/arquivos, ainda não encontrei problemas (…mas eles podem estar lá ^u^):

#!/usr/bin/env bash

find "$1" -depth -print0 | while IFS= read -r -d '' file; do
  d="$( dirname "$file" )"
  f="$( basename "$file" )"
  new="$(echo $f | sed 's/ (.*)//g' | sed 's/ /_/g' | tr -dc '[:alnum:]._-' | sed 's/__//g')"
  if [ "$f" != "$new" ]      # if equal, name is already clean, so leave alone
  then
    if [ -e "$d/$new" ]
    then
      echo "Notice: \"$new\" and \"$f\" both exist in "$d":"
      ls -ld "$d/$new" "$d/$f"
    else
      echo mv "$file" "$d/$new"      # remove "echo" to actually rename things
    fi
  fi
done

O primeiro sed elimina o bloco final do nome do arquivo, daí vem os que você ensinou e, o último, dá cabo dos underlines consecutivos.

Muito obrigado!