Excluir espaços, hífens e sublinhados nos nomes de arquivos?

10

Qual é um bom comando para excluir espaços, hífens e sublinhados de todos os arquivos em um diretório ou arquivos selecionados?

Eu uso o seguinte comando com Thunar Custom Actions para slugify nomes de arquivos:

for file in %N; do mv "$file" "$(echo "$file" | tr -s ' ' | tr ' A-Z' '-a-z' | tr -s '-' | tr -c '[:alnum:][:cntrl:].' '-')"; done

Mas esse comando substitui apenas espaços por traços / hífens e caracteres com letras minúsculas.

Eu usei o seguinte comando no terminal para excluir espaços de milhares de nomes de arquivos em uma pasta e funcionou muito rápido:

 rename "s/ //g" *

Novamente, ele exclui apenas espaços, e não hífens / traços e sublinhados também.

Idealmente, não quero espaços, hífens / traços e sublinhados nos meus nomes de arquivos. E seria ótimo se o comando pudesse ser usado com ações personalizadas da Thunar nos arquivos selecionados.

user8547
fonte
2
Observo um problema que muitas das soluções propostas têm, não é verificar adequadamente a existência do "novo" nome antes de mover o arquivo. Não fazer isso pode ser a fonte potencial de muitos problemas.
Mdpc
É possível modificar o comando de John1024 para verificar isso?
user8547
@ user8547rename -i "s/[-_ ]//g" *
Sparhawk
Obrigado Sparhawk. Aliás, para aqueles interessados ​​em usar isso como uma ação personalizada Thunar, o comando para Thunar é: para arquivo em% N; mv "$ file" echo $file | sed -e 's/[ _-]//g'; done
user8547

Respostas:

11

A versão renamedisso vem com o perlpacote suporta expressões regulares:

rename "s/[-_ ]//g" *

Alternativamente,

rename -i "s/[-_ ]//g" *

O -isinalizador fará renameuso do modo interativo, perguntando se o destino já existe, em vez de substituir silenciosamente.

O nome do Perl é chamado às vezes prename.

Renomeação de Perl versus renomeação de util-linux

Em sistemas parecidos com o Debian, a renomeação do perl parece ser o padrão e os comandos acima devem funcionar.

Em algumas distribuições, o renameutilitário do util-linux é o padrão. Este utilitário é completamente incompatível com o Perl rename.

  • Todos: Primeiro, verifique se o Perl renameestá disponível sob o nome prename.

  • Debian: A renomeação do Perl deve ser o padrão. Também está disponível como prename. O renameexecutável, porém, está sob o controle /etc/alternativese, portanto, poderia ter sido alterado para algo diferente.

  • archlinux: Execute pacman -S perl-renamee o comando está disponível como perl-rename. Para um nome mais conveniente, crie um alias. (Dica do chapéu: ChiseledAbs)

  • Mac OSX De acordo com esta resposta , renamepode ser instalado no OSX usando homebrew via:

    brew install rename 
  • Download direto: rename também está disponível em Perl Monks:

     wget 'http://www.perlmonks.org/?displaytype=displaycode;node_id=303814' -O rename
John1024
fonte
Eu acho que depende do que renamevocê está falando. O do util-linux -2.24.2-1.fc20.x86_64 não suporta expressões regulares.
Cristian Ciupitu
1
@CristianCiupitu Acabei de verificar a página de manual quanto à versão de renomeação que você encontrou. Com base nos argumentos, a versão renameque o OP estava usando se parece com a perlversão e não com a util-linuxversão.
precisa saber é o seguinte
Para o registro, esta é a renamepágina de manual da versão util-linux . De qualquer forma, além dessa nota, o importante é que o OP tenha a resposta dele (e você fez um voto positivo :-D).
Cristian Ciupitu
@CristianCiupitu Obrigado por encontrar isso. De volta para você com um +1.
precisa saber é o seguinte
1
@ John1024 archlinux, mas eu descobri como, basta ir, pacman -S perl-renameentão eu acho que você pode alias.
ChiseledAbs
5

Eu substituiria todos esses trcomandos, por um sedcomando de substituição, por exemplo:

for file in %N; do 
    mv "$file" "$(echo "$file" | sed 's/[ _-]//g')"
done
Cristian Ciupitu
fonte
4

Sem contar mv, você realmente não precisa de um processo de fora para este em tudo - você pode tipo de apenas poof -los.

ifsqz() ( LC_ALL=C sqz=$1
    isf() { [ -e "$1" ] || [ -L "$1" ] ; }  
    set -- * ; set -f
    for f do isf "$f" || break
    IFS=$sqz; set -- $f; IFS=
    isf "$*" || mv -- "$f" "$*"
    done
)

Ainda assim, isso significa uma mvchamada por arquivo e, provavelmente, renameé melhor. Embora isso deve funcionar dado apenas um POSIX mvem $PATHe um shell POSIX.

Então, eu vim com uma espécie de demonstração louca por isso. O conjunto de testes é gerado como:

tee - - - - <<CGEN |\
dd cbs=90 conv=unblock |\
sed 'G;$!N'";s/^/touch -- '/;s/$/'/" |sh
$( #BEGIN CGEN
   LC_ALL=C
   i= n='"$((i=((i=i+1)==10||i==39||i==47)>0?(i+1):i))"'
   printf '%b -_   ---___'  $(
   IFS=0; eval \
       printf '"\\\\%04o\\\\%04o "' "$(
       printf "$n"' "$i" '%s $(
       printf %.252d
#END
))"))
CGEN

Em primeiro lugar, serei o primeiro a reconhecer que o comando acima produz resultados que podem ser obtidos mais facilmente por outros meios. Mas outros meios provavelmente não demonstrariam tão bem o que poderia ser feito $IFSe um pouco de imaginação (doentia) .

Portanto, o primeiro bit é bastante direto:

  • tee canaliza 5 cópias de sua entrada - o documento hereditário CGEN

  • dd bloqueia sua entrada por novas linhas a 90 bytes por bloco e canaliza para ...

  • sedune 2 desses blocos em dois \ncaracteres ewline, 'aspas simples os resultados e precede a sequência touch --de caracteres para cada ciclo de linha antes de ...

  • sh que executa todas as entradas como comandos do shell

O #CGENpouco embora ... Bem, brevemente ...

  • a parte inferior printfimprime 252 0s

  • o próximo da última recebe 252 ''argumentos de cadeia nula e, para cada um, imprime o conteúdo de $nseguido pela cadeia" $i "

  • evalinterpreta os argumentos do próximo printfitem antes de imprimir os resultados dessa interpretação como números octais, precedidos por 2 barras invertidas por peça

  • o último printfimprime os valores de bytes para os octais 2 de cada vez, seguidos pela sequência -_ ---___de caracteres para cada par

  • $né inicializado com uma equação que será incrementada $iem um para cada avaliação, exceto pelo fato de ignorar os valores 10, 39 ou 47 - (que são linha de \new, aspas 'simples e /barra em decimal ASCII, respectivamente)

O resultado final é um diretório que contém muitos nomes de arquivos realmente feios, contendo todos os bytes no meu conjunto de caracteres de 1 a 255, exceto as aspas simples (puladas para evitar mais uma sed s///declaração) e a /barra. Esses nomes de arquivos são assim:

(set -- *; printf '%s\n\n##############\n\n%s\n' "${9}" "${34}")  | cat -A

   ---___ww -_   ---___xx -_   ---___yy -_   ---___zz -_   ---___{{ -_   ---___|| -_   ---$
$
___}} -_   ---___~~ -_   ---___^?^? -_   ---___M-^@M-^@ -_   ---___M-^AM-^A -_   ---___M-^BM-^B -_   ---___M-^CM-^C$
$
##############$
$
 -_   ---___M-ZM-Z -_   ---___M-[M-[ -_   ---___M-\M-\ -_   ---___M-]M-] -_   ---___M-^M-^ -_   ---___M-_M-_ -_$
$
---___M-`M-` -_   ---___M-aM-a -_   ---___M-bM-b -_   ---___M-cM-c -_   ---___M-dM-d -_   ---___M-eM-e -_   ---___$

Agora vou obter alguns dados sobre esses arquivos:

chksqz() ( LC_ALL=C sqz=$1
    set -- * ; set -f ; IFS= ; tc="$*"
    printf '#%s\n' \
        "There are $# files in this test directory." \
        "All filenames combined contain a total of ${#tc} bytes."
    IFS=$sqz ; set -- $* ; IFS= ; sc="$*"  
    printf "%s '$sqz'" \
        "#Of which ${#sc} bytes are not"\
        " and $((${#tc}-${#sc})) bytes are"
    set +f ; unset IFS
    printf ".\n#%s\n#Total:\t%d\n#Other:\t%d\n#'$sqz':\t%d\n" \
        "And to confirm these figures:" \
        $(  printf %s * | wc -c 
            printf %s * | tr -d "$sqz" | wc -c
            printf %s * | tr -dc "$sqz" | wc -c
))
chksqz '_ -'

RESULTADO

#There are 101 files in this test directory.
#All filenames combined contain a total of 17744 bytes.
#Of which 2692 bytes are not '_ -' and 15052 bytes are '_ -'.
#And to confirm these figures:
#Total: 17744
#Other: 2692
#'_ -': 15052

Está bem. Agora, finalmente, para a ação:

ifsqz '_ -'
chksqz '_ -'

RESULTADO

#There are 101 files in this test directory.
#All filenames combined contain a total of 2692 bytes.
#Of which 2692 bytes are not '_ -' and 0 bytes are '_ -'.
#And to confirm these figures:
#Total: 2692
#Other: 2692
#'_ -': 0

Sucesso! Você pode ver por si mesmo:

ls

????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
???????????????????????????
???????????????????????????
???????????????????????????
????????????????????????????
????????????????????????????
????????????????
??????????????????????
????????????????????????
??????????????????????????
??????????????????????????
??????????????????????????
??????????????????????????
???????????????????????????
???????????????????????????
???????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
??????????????????????????
????????????????????????
????????????????????
??????????????????
????????????????????????????
??
????????????????????????????
??????????????????????????
????????????????????????????
????????????????????????????
????????????????????!!""##
??????????????????!!""##$$
????????????????!!""##$$%%
????????????!!""##$$%%&&((
????????!!""??##$$%%&&(())
$$%%&&(())**??++,,..0011
%%&&(())**++??,,..00112233
&&(())**++,,??..0011223344
))**++,,..??0011223344556
**++,,..00??11223344556677
22334455667788??99::;;<<==>>
445566778899??::;;<<==>>??@@
5566778899::;;??<<==>>??@@AA
6778899::;;<<??==>>??@@AABB
8899::;;<<==??>>??@@AABBCCDD
\\]]^^``aa??bbccddeeffgghh
]]^^``aabbc??cddeeffgghhii
^^``aabbccdd??eeffgghhiijj
??@@AABBCCDDEE??FFGGHHIIJJKK
AABBCCDDEEFF??GGHHIIJJKKLLM
BBCCDDEEFFGG??HHIIJJKKLLMMNN
CCDDEEFFGGHHII??JJKKLLMMNNOO
EEFFGGHHIIJJ??KKLLMMNNOOPPQQ
ffgghhiijjkk??llmmnnooppqqrr
gghhiijjkkllmm??nnooppqqrrss
iijjkkllmmnn??ooppqqrrsstt
jjkkllmmnnoo??ppqqrrssttuuvv
kkllmmnnooppqq??rrssttuuvvww
LLMMNNOOPPQQRR??SSTTUUVVWWXX
MNNOOPPQQRRSS??TTUUVVWWXXYY
OOPPQQRRSSTT??UUVVWWXXYYZZ[[
PPQQRRSSTTUUVV??WWXXYYZZ[[\\
RRSSTTUUVVWW??XXYYZZ[[\\]]
ssttuuvvwwxx??yyzz{{||}}~~??
ttuuvvwwxxyyz??z{{||}}~~????
uuvvwwxxyyzz{{??||}}~~??????
wwxxyyzz{{||??}}~~??????????
xxyyzz{{||}}~~??????????????
YYZZ[[\\]]^^??``aabbccddee
ZZ[[\\]]^^``??aabbccddeeff
mikeserv
fonte
2
+1 para um uso criativo de IFS+printf
John1024
@ John1024 - o que é muito divertido:set -- 'some arbitrary' args; eval printf '"%s\n"' "$(IFS=0; printf ' "$@" %s' $(printf %025d))"
mikeserv
1
new="$(IFS=" -_"; printf %s $1)"bifurca um subshell (exceto no ksh93) e tem problemas com as novas linhas de cauda. Outra opção é o uso IFS=' -_'; set -- $1; IFS=; new="$*"(e mudar seu loop while para um loop)
Stéphane Chazelas
1
[ -e x ]retornará false se xfor um link simbólico para um arquivo inexistente ou não acessível.
Stéphane Chazelas
1
Boa concha Kung-Fu!
countermode
2

se você tem perl, geralmente renomeia. você pode fazer:

> type rename
rename is /usr/bin/rename

e mostre como esse script está escrito:

> cat /usr/bin/rename | head -n 5 #firt 5 lines for example
#!/usr/bin/perl -w
#
#  This script was developed by Robin Barker ([email protected]),
#  from Larry Wall's original script eg/rename from the perl source.
#

Este script não suporta a sinalização -i (esta é a versão no meu sistema), mas talvez o seu suporte. E os argumentos. Primeiro é expressões regulares com o formato PCRE, funciona como filtro, modifica o nome da entrada para o nome da saída. Lista de nomes de entrada fornecidos pelo asterisco '*'. por exemplo, você faz:

> cd /tmp
> rename 's/ //g' *

em real '*' pode ser expandido para:

> rename 's/ //g' file1 file2 file3 othe files found in current directory

Quando você tem arquivos de contagem realmente grandes, está preso. O shell expandirá sua linha por mais tempo do que o sistema aceita. então você pode executar uma solução alternativa usando find ou xargs. usar 'find' é um problema, porque renomear será chamado muitas vezes igual à contagem de arquivos no diretório melhor usar xargs com a opção -r. uma chamada de renomeação modifica muitos arquivos. por exemplo:

> ls | xargs -r rename 's/ //g'   #thats all, names will be appended at the end of this command.

último problema, o que significa:

's/ //g'

esta é uma expressão regular para modificar nomes. depois do primeiro '/' é o espaço. isso é detectado e substituído por uma sequência após o segundo '/'. Mas há uma string vazia terminada com o terceiro '/', depois o espaço é substituído por nada. A opção 'g' torna essa expressão repetitiva. A expressão percorrerá todo o nome do começo ao fim e detectará todos os espaços.

Mas e se você tiver um caractere de tabulação ou outro caractere 'branco'? há substituto para esse '\ s'. que outros personagens desnecessários? basta adicioná-lo à expressão. Todos fecham com colchetes, por exemplo:

's/[\s_-]//g'

Isso é tudo. você vê similaridade? Acho que você deveria ler man perlrequick e man perlretut, isso explica (espero) como a expressão regular funciona. você pode usar o comando rename em seu próprio script, se precisar.

Znik
fonte
1

O seguinte shloop de shell removerá todos os espaços, sublinhados e hífens dos nomes dos arquivos no diretório atual, tomando o cuidado de não sobrescrever nenhum arquivo existente:

for f in *; do
    test -f "$f" || continue
    nf=$( echo "$f" | tr -d ' _-' )
    ! test -e "$nf" && echo mv "$f" "$nf"
done

Para bashe kshsendo um pouco mais detalhado com a lógica:

for f in *; do
    if [[ -f "$f" ]]; then
        nf=$( tr -d ' _-' <<<"$f" )
        if [[ ! -e "$nf" ]]; then
            echo mv "$f" "$nf"
        fi
    fi
done

Remova o echoquando tiver certeza de que faz o que deseja.

O trcomando excluirá ( -d) qualquer caractere no conjunto de caracteres especificado ( ' _-'). É importante ter o traço no início ou no final do conjunto, ou será interpretado como um intervalo de caracteres.

Kusalananda
fonte