ODEIO espaços nos nomes de arquivo

61

É simples Não suporto quando as pessoas usam espaços ao nomear arquivos. Às vezes, destrói os comandos do console e torna a saída de ls feia.

O desafio é escrever um programa (apenas caracteres ascii) que

  1. renomeia todos os arquivos (incluindo diretórios) no diretório atual para versões com espaços removidos ou substituídos por '_'
  2. em caso de colisão, é necessário anexar um identificador exclusivo (até você)
  3. desce recursivamente em todos os subdiretórios

Você pode assumir nomes de caminho no estilo UNIX. Quem precisaria desse programa em uma máquina Windows?

Isso é código de golfe, o programa mais curto vence (caracteres #ascii). Como eu odeio tanto os espaços, cada espaço deve ser contado duas vezes.

Forneça seu idioma, pontuação, programa e uma breve descrição de como executá-lo.

O programa deve compilar e executar com um esforço razoável na minha máquina Linux.

EDIT: Como Etan solicitou uma estrutura de arquivos para teste, eis o script que atualmente uso para criar uma árvore de arquivos adequada:

#!/bin/bash
rm -r TestDir

touchfiles()
{
    touch my_file
    touch my__file
    touch "my file"
    touch "my  file"
    touch " my_file  "
}

mkdir TestDir
cd TestDir

touchfiles

for dir in "Test Sub" Test_Sub "Te stSub" Te_stSub
do
    mkdir "$dir"
    cd "$dir"
    touchfiles
    cd ..
done
M.Herzkamp
fonte
22
Isso está implorando por uma solução feita sem caracteres ASCII.
Dennis Jaheruddin
50
Agora eu quero aprender espaço em branco
BrunoJ
10
O @BrunoJ fazendo isso no Whitespace primeiro exigiria que você desenvolvesse um sistema de acesso a arquivos no WS. Eu acho que seria mais desafiador do que o desafio real.
Nzall 07/08
7
Esperando alguém postar uma solução C / C ++ para que eu possa roubá-la, compilar, publicar em hexadecimal como código de máquina x86 com espaços ZERO! [ou talvez base64]
Mark K Cowan
10
Eu odeio sublinhados em nomes de arquivos. Use traços.
perfil completo de Rebmu

Respostas:

10

Coreutils Zsh + GNU - 48 bytes (1 espaço)

for x   (**/*(Dod))mv   -T  --b=t   $x  $x:h/${${x:t}// }

É estranho que você odeie espaços (ASCII), mas esteja bem com guias e novas linhas, mas acho que são necessários todos os tipos.

O zmv resolve muitos problemas de renomeação de arquivos de forma concisa (e apenas um pouco obscura). No entanto, insiste em que os alvos sejam únicos; embora você possa adicionar sufixos exclusivos com facilidade, adicionar um sufixo apenas se for necessário praticamente requer refazer todo o trabalho. Então, em vez disso, faço um loop manualmente e confio no GNU mv para anexar um identificador exclusivo em caso de colisão ( --backupopção, além disso --no-target-directory, caso um destino seja um diretório existente, caso contrário mv, moveria a fonte dentro desse diretório).

(od)é um qualificador global para classificar a saída com os diretórios que aparecem após o conteúdo (como os do find -depth). Dinclui arquivos de ponto na glob. :he :tsão modificadores de histórico semelhantes a dirnamee basename.

mvreclama que foi chamado para renomear arquivos para si próprios, porque a glob inclui nomes de arquivos sem espaços. É a vida.

Versão não destruída:

for x in **/*\ *(Dod); do
  mv --no-target-directory --backup=numbered $x ${x:h}/${${x:t}// /}
done
Gilles 'SO- parar de ser mau'
fonte
11
isso não renomeia meus arquivos!
M.Herzkamp
@ M.Herzkamp Oh, certo, as zmvbombas antes mvtêm a chance de resolver colisões. Ok, estou fazendo isso manualmente. Acabará sendo exatamente do mesmo tamanho se eu pular arquivos de ponto e até salvar um caractere se não o fizer.
Gilles 'SO- stop be evil' -
11
Agora está funcionando. BTW: Eu incluí a pena de espaço em um momento em que eu realmente tinha um rancor contra espaços;) Ironicamente, eu não excluir espaços quando eu postei o desafio: P
M.Herzkamp
13

Bash 116 bytes, 16 espaços

find . -depth -exec bash -c 'B=${0##*/}
M="${0%/*}/${B// /_}"
while [ -e "$M" ]
do M=$M.
done
mv "$0" "$M"' {} \;

Não suprimi erros para obter mais alguns bytes. Isso não terá colisões.

Se um GNU não posix findpuder ser esperado, isso poderá ser reduzido ainda mais:

Bash 110 bytes, 15 espaços

find -d -exec bash -c 'B=${0##*/}
M="${0%/*}/${B// /_}"
while [ -e "$M" ]
do M=$M.
done
mv "$0" "$M"' {} \;

A remoção de espaços em vez de substituí-los usa dois bytes a menos:

Bash 108 bytes, 15 espaços

find -d -exec bash -c 'B=${0##*/}
M="${0%/*}/${B// }"
while [ -e "$M" ]
do M=$M.
done
mv "$0" "$M"' {} \;

Nota: se as guias puderem ser usadas em vez de espaços, será necessário apenas 1 espaço (aquele na regra de correspondência para substituição na linha 2).

Agradecimentos a Dennis por encontrar o erro entre aspas duplas (e fornecer a solução)

pqnet
fonte
11
O ESPAÇO EXTRA ATRÁS DE ENCONTRAR LÁ PARA ME ZOCAR ??? ;-)
M.Herzkamp
@ M.Herzkamp Eu pensei que era um erro de copiar e colar, mas na verdade está lá. Acho que ganhei mais 2 pontos. Além disso, -depthno GNU pode ser substituído por -d, apesar de reclamar que está obsoleto. Não sei as regras do golfe, posso fazer isso?
Pqnet 6/14
2
Contanto que funcione, eu permito. Caso a remoção depreciação tornar-se em uma versão futura, porém, eu poderia ter de voltar a esta resposta e downvote-lo por não estar correta ;-)
M.Herzkamp
2
Isso não funcionará corretamente se algum dos nomes de arquivo contiver aspas duplas. Para corrigir isso, você pode usar o bash -c 'B=${0##*/}...' {} \;que é mais curto.
813 Dennis
3
Eu acho que vou ser esse cara, o que há com a Nvariável? Nunca é definida ...
Steven Penny
9

Python 180 bytes

from    os  import*
t,c,h='.',chdir,path
def g(p):
    c(p)
    for x   in  listdir(t):
        if h.isdir(x):g(x)
        n=x.replace(' ','')
        while h.exists(n):n+=t
        if' 'in x:rename(x,n)
    c(t*2)
g(t)

apenas 2 espaços se você usar tab para recuo :-)

Emanuele Paolini
fonte
Eu acho que a maioria das outras respostas poderia melhorar sua pontuação usando guias em vez de espaços também.
precisa saber é o seguinte
Mas seu envio usa espaços, não é? (+1 para o código de trabalho)
M.Herzkamp
Eu não sei como att caracteres de tabulação na resposta ...
Emanuele Paolini
2
substituído por tabs :-)
Emanuele Paolini 07/08
3
Como feio ... Bem, acho que eu pedi-lo :(
M.Herzkamp
5

Se a ordem dos sufixos de arquivo colidido não precisar dar precedentes ao arquivo preexistente, o seguinte funcionará para mim:

bash / find / mv 84 bytes, 16 espaços

find -depth -execdir bash -c '[ "${0//[^ ]}" ] && mv -{T,--b=t} "$0" "${0// }"' {} \;

bash / find / mv 82 bytes, 14 espaços

find -depth -execdir bash -c '[ "${0//[^ ]}" ]&&mv -{T,-b=t} "$0" "${0// }"' {} \;

Abraçado &&para salvar dois bytes de espaço.

bash / find / mv 60 bytes, 11 espaços

find -d -execdir bash -c 'mv -{T,-b=t} "$0" "${0// }"' {} \;

Abaixa a proteção contra erros para obter erros do mv em arquivos que não têm espaço para começar.

Edit: Soltou as aspas de {}como lembrado por Dennis. Também é permitido findgritar sobre portabilidade e reprovação na versão mais curta, onde mvjá está gritando sobre mover um arquivo por cima de si.

Editar 2: Adicionado -Tao mvcomando para evitar o aninhamento de diretórios em vez de renomear, conforme indicado pelo pqnet. Expansão de chave usada ao custo de um caractere, usando apenas um espaço.

Etan Reisner
fonte
Você pode usar em -dvez de -depthe não precisa das aspas {}.
Dennis
@Dennis Yeah. Vi a -dconversa na resposta do pqnet, mas imaginei que, como estava silenciando os mvgritos, evitaria os findgritos. Embora eu devesse encurtá-lo para o grito. E sim, eu sempre cito {}por algum motivo, mesmo sabendo que não é necessário nesse caso. Força do hábito, eu acho.
Etan Reisner
11
Quando ocorrer uma colisão nos nomes dos diretórios, eles serão colocados um no outro (e não serão removidos os espaços). Use -Topção para mvevitar isso
pqnet
Isso funciona, e eu disse no desafio que o apêndice é com você. 1
M.Herzkamp
4

NodeJS - 209 bytes, 3 espaços em branco

s=require('fs');function a(d){s.readdirSync(d).forEach(function(f){f=d+'/'+f;i=0;r=f;if(/ /.test(f)){r=f.replace(' ','');while(s.existsSync(r))r+=i++;s.renameSync(f,r)}s.statSync(r).isDirectory()&&a(r)})}a('.');
cPu1
fonte
Não estou familiarizado com o node.js. Como eu iria executá-lo?
M.Herzkamp
Você precisará do nodejs executável do Node ; guardá-lo em um arquivo e executarnode file.js
CPU1
7
Eu recebo uma exceção TypeError: Object #<Object> has no method 'exists'. Adivinhe onde: está na linha 1! : D
M.Herzkamp
Eu testei. De qualquer forma, substituí existe pelo seu equivalente síncrono. Você pode tentar agora?
CPu1
11
Eu tenho apenas a versão 0.6.12 instalada. Este pode ser o problema.
M.Herzkamp
2

Bash - 86 bytes

find    .   -d|while    IFS=""  read    f;do    t=${f##*/};mv   --b=t   -T  "$f"    "${f%/*}"/${t// /};done
Subbeh
fonte
Opa, vamos dar uma olhada
Subbeh
2
Além disso, os espaços são contados duas vezes ;-)
M.Herzkamp
o que exatamente você quer dizer com espaços são contados duas vezes?
Subbeh
11
Você pode salvar muitos caracteres abreviando --backuppara--b
11
Sim, agora também funciona com o meu conjunto de testes! 1
M.Herzkamp
2

Bash + Perl rename64

( renameé o script Perl no Debian e derivados, não o comando util-linux.)

find . -depth -name "* *" -execdir rename 'y/ /_/' * \;
german_guy
fonte
11
O que acontece se "my file.txt" e "my_file.txt" estiverem presentes?
M.Herzkamp
11
Oh verdade .. Trabalhando sobre isso em breve
german_guy
11
*como deve ser {}, apenas renomeia os arquivos cujo nome aparece no diretório atual. Isso não acrescenta um sufixo em caso de colisão. Você pode economizar um pouco ao omitir, -name "* *"pois renameignora silenciosamente os arquivos cujo nome não é transformado.
Gilles 'SO- stop be evil' em
2

POSIX sh+ GNU find+ GNU mv67 bytes ASCII + um espaço (literal)

find    -d  -exec   sh  -cf 'IFS=\ ;IFS=_   set $0;mv   --b=t   "$0"    "$*"'   {}  \;

Não sei se ele se encaixa, mas com isso qualquer sequência de espaços é unida a um único _- eu gosto mesmo assim. Na verdade, qualquer sequência, exceto os espaços iniciais / finais que são - são automaticamente truncados (o que também é, penso eu, um comportamento benéfico) . Obrigado a Gilles por apontar isso.

Isso apenas usa o separador de campo interno para separar os campos.

É bastante ... falador ...

...Oh cara. Eu sabia que a coisa da guia era barata, mas achei que era pelo menos inteligente. Agora estou atrasado para a festa ...

mikeserv
fonte
Isso funciona no meu conjunto de testes como você pretendia, mas não como o desafio exige. Eu gosto, porém, porque provavelmente vou aprender algo novo. Eu acho que vou ter que ler sobre isso IFScoisinha magia ...
M.Herzkamp
11
@ M.Herzkamp - ifs se comporta de maneira diferente dependendo se está definido como espaço em branco ou não. A maioria das pessoas odeia isso porque não entende suas duas qualidades principais - que ele opera apenas em expansões ( $expandnão (ex pand)) e na coisa ifsws mencionada. Veja aqui
mikeserv 11/08/14
Isso não renomeia arquivos dentro de diretórios cujos nomes contêm espaços. Uma correção seria substituir -execpor -execdir. Outra peculiaridade IFSque você não está mencionando é que os espaços finais são excluídos. Observe que, como outros observaram, você também precisa da -Topção mv, para quando o destino de uma mvchamada for um diretório existente.
Gilles 'SO- stop be evil' -
@Gilles - minha preferência seria usar sh -c 'mkdir -p ../newtree/"$0"; ln "$0"/* ../newtree/$0 {} \;e outros globs em um find -type dcomando para criar uma árvore espelhada de links físicos e depois operar com eles, mas estou pensando em escrever um código de golfe para uma operação de movimentação. Bom ponto sobre os espaços iniciais / finais, embora eu ache que esse também seja um comportamento que eu preferiria.
mikeserv
@Gilles - mas, a propósito, não é uma peculiaridade - é um comportamento pretendido e controlado por padrões . A seção Divisão de campo está entre as poucas na especificação do shell que não contém as palavras não especificadas ou definidas pela implementação . Não existem tais garantias com zsha função incorporada, zmv por exemplo.
mikeserv
2

PHP, 147 145 bytes, 2 1 espaço s -> 146

function    s(){foreach(glob("*")as$n){is_dir($n)&&chdir($n)&s()|chdir("..");if($n<$r=strtr($n," ",_)){while(file_exists($r))$r.=_;rename($n,$r);}}}

função recursiva. Correr coms(".");

Repita os globresultados para o caminho especificado:

  • se diretório, recurs
  • substituir espaços por sublinhado
  • se as cordas diferirem
    • enquanto um novo nome de arquivo for obtido, acrescente sublinhado
    • renomear arquivo / diretório
Titus
fonte
php irá renomear os arquivos no servidor ... Agora eu me pergunto como alterar os nomes de um cliente de arquivo sempre que visitar o seu site: D
M.Herzkamp
1

Ruby 121

require 'find'

Find.find('.') do |file|
  if file.chomp.match(/ /)
    File.rename(file, file.gsub(/ /, '_'))
  end
end
gam3
fonte
6
Bem-vindo ao Code Golf! A idéia aqui nesses desafios de código-golfe é usar o menor número de caracteres. Isso significa que você pode definitivamente se livrar de linhas e guias em branco e criar nomes de variáveis ​​com um caractere, mas as pessoas procuram todo tipo de maneira criativa de reduzir a contagem de caracteres.
Não que Charles
Eu recebo um erro, que o diretório não está vazio:gam3.rb:5:in `rename': Directory not empty - ./Te stSub or ./Te_stSub (Errno::ENOTEMPTY) from gam3.rb:5 from /usr/lib/ruby/1.8/find.rb:39:in `find' from /usr/lib/ruby/1.8/find.rb:38:in `catch' from /usr/lib/ruby/1.8/find.rb:38:in `find' from gam3.rb:3
M.Herzkamp
1

Python, 187

165, mais 22 pontos de penalidade para os espaços.

from os import*
u='_';j=path.join
for t,d,f in walk('.',0):
 for z in f+d:
  n=z.replace(' ',u)
  if n!=z:
   while path.exists(j(t,n)):n+=u
   rename(j(t,z),j(t,n))

166, usando o truque de Emanuele :

Apenas um único espaço neste!

from    os  import*
u='_';j=path.join
for t,d,f   in  walk('.',0):
    for z   in  f+d:
        n=z.replace(' ',u)
        if  n!=z:
            while   path.exists(j(t,n)):n+=u
            rename(j(t,z),j(t,n))
Henry Keiter
fonte
Isso funciona para mim. 1
M.Herzkamp 8/08/14
remover os espaços no início das linhas e guias de uso - eles existem espaços para contar apenas uma vez
chill0r
@ chill0r É assim que a segunda versão é; todos os espaços são substituídos por tabulações, exceto uma (exceto que o SO ainda as exibe como espaços).
Henry Keiter
1

LiveScript - 166

(Substitua espaços por tabulações.)

(a=->(s=require \fs)readdirSync(it)map (f)->f=it+'/'+f;r=f.replace /\s/g,i='';(while f!=r&&s.existsSync r=>r+=i++);s.statSync(f)isDirectory(s.renameSync f,r)&&a r) \.

Baseado no de nderscore versão otimizada da CPU1 's resposta .

nyuszika7h
fonte
Trabalho! +1 Vou excluir meus comentários anteriormente para arrumar esta postagem.
M.Herzkamp
0

Bash 4+ 111 bytes

shopt -s dotglob globstar
for f in **
do
n=${f// /}
while [[ $f != $n && -e $n ]]
do n+=1
done
mv "$f" $n
done

fonte
11
Mesmos problemas que várias outras entradas: Você substitui espaços nos diretórios pai e o mv não pode encontrá-los. Você também deve alterar a direção da travessia, caso contrário, renomeia os diretórios e o mv não consegue encontrar os arquivos.
M.Herzkamp 8/08/14
0

Groovy, 139 caracteres

def c
c={
f->
def g=new File(f.parent,f.name.replaceAll('\\s',''))
f.renameTo(g)
!g.directory ?: g.eachFile(c)
}
new File('.').eachFile(c)

de acordo com @ edc65 comment

Groovy, manipula colisões, 259 caracteres

def c
c={
p,l,f->
def g=new File(p,f.name.replaceAll('\\s',''))
f==g?:
(g.exists()?f.renameTo(g.toString()+l.indexOf(f.name)):f.renameTo(g))
!g.directory?:g.eachFile(c.curry(g,g.list().toList()))
}
def r=new File('.')
r.eachFile(c.curry(r,r.list().toList()))
Conecte-se
fonte
11
Isso não lida com colisões.
edc65 13/08
Certifique-se de que os arquivos sejam renomeados antes dos diretórios-pai e que os espaços nos diretórios-pai não sejam substituídos.
M.Herzkamp
Eu tenho certeza que está tudo bem
login
0

POSIX (testado no zsh) + comandos básicos do Linux 151

export IFS='
'
for f in $(ls -R1);do export n=$(echo $f|tr ' ' '_');yes n|mv $f $n || yes n|mv $f `echo $n;echo $f|md5sum`
done
LinGeek
fonte
@ M.Herzkamp Fixed.
precisa saber é o seguinte
Várias coisas: qual é a função da exportação IFS ec em ls -cR? E qual versão do mv você precisa para a opção --reply? (Eu tenho 8.13, e não reconhece a opção). Também para obter uma pontuação melhor, você deve abreviar os nomes das variáveis.
M.Herzkamp
OC substitui espaços por novas linhas. O IFS interrompe os espaços como separadores. O --reply é de versões antigas e está prestes a ser corrigido.
precisa saber é o seguinte
11
Está faltando um segundo mv na linha 5? E acho que um eco nessa linha está errado.
M.Herzkamp
11
$(ls -CR)é completamente falso. A -copção é inútil e -Rgera arquivos sem o diretório deles, o que é inútil. Sua arquitetura fundamentalmente não manipula nomes de arquivos contendo novas linhas. Você precisa set -fou os nomes dos arquivos que contêm curingas explodirão. exporté inútil. Eu posso ver vagamente o que você está tentando fazer para unificar arquivos, mas a tubulação está errada.
Gilles 'SO- stop be evil' em