ODIO los espacios en los nombres de archivo

61

Es simple. No puedo soportar cuando la gente usa espacios al nombrar archivos. A veces destruye los comandos de la consola y hace que la salida de ls sea fea.

El desafío es escribir un programa (solo caracteres ascii) que

  1. cambia el nombre de todos los archivos (incluidos los directorios) en el directorio actual a versiones con espacios eliminados o reemplazados por '_'
  2. en caso de colisión, debe agregar un identificador único (depende de usted)
  3. desciende recursivamente en todos los subdirectorios

Puede asumir nombres de ruta de estilo UNIX. ¿Quién necesitaría este programa en una máquina Windows de todos modos?

Este es el código de golf, el programa más corto gana (#ascii caracteres). Como odio tanto los espacios, cada espacio tiene que contarse dos veces.

Proporcione su idioma, puntaje, programa y una breve descripción de cómo ejecutarlo.

El programa debe compilarse y ejecutarse con un esfuerzo razonable en mi máquina Linux.

EDITAR: como Etan solicitó una estructura de archivos para las pruebas, aquí está el script que utilizo actualmente para crear un árbol de archivos adecuado:

#!/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
fuente
22
Esto es pedir una solución hecha sin caracteres ascii.
Dennis Jaheruddin
50
Ahora quiero aprender Whitespace
BrunoJ
10
@BrunoJ haciendo esto en Whitespace primero requeriría que desarrolle un sistema de acceso a archivos en WS. Creo que eso sería más desafiante que el desafío real.
Nzall
77
¡Esperando a que alguien publique una solución C / C ++ para poder robarla, compilarla, publicarla en hexadecimal como código de máquina x86 con CERO espacios! [o tal vez base64]
Mark K Cowan
10
Odio los guiones bajos en los nombres de archivo. Usa guiones.
Dr. Rebmu

Respuestas:

10

Coreutils Zsh + GNU - 48 bytes (1 espacio)

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

Es extraño que odies los espacios (ASCII), pero estás bien con pestañas y líneas nuevas, pero supongo que se necesita todo tipo.

zmv resuelve muchos problemas de cambio de nombre de archivos de manera concisa (y solo un poco oscura). Sin embargo, insiste en que los objetivos sean únicos; Si bien puede agregar fácilmente sufijos únicos, agregar un sufijo solo si fuera necesario prácticamente requiere volver a hacer todo el trabajo. Entonces, en cambio, hago un bucle manualmente y confío en GNU mv para agregar un identificador único en caso de colisión ( --backupopción, más --no-target-directoryen caso de que un destino sea un directorio existente, ya que de mvlo contrario se movería la fuente dentro de ese directorio).

(od)es un calificador global para ordenar la salida con directorios que aparecen después de su contenido (como los de find -depth). Dincluye archivos de puntos en el globo. :hy :tson modificadores de historia similares a dirnamey basename.

mvse queja de que se llama para cambiar el nombre de los archivos a sí mismos, porque el globo incluye nombres de archivos sin espacios. Así es la vida.

Versión sin golf:

for x in **/*\ *(Dod); do
  mv --no-target-directory --backup=numbered $x ${x:h}/${${x:t}// /}
done
Gilles 'SO- deja de ser malvado'
fuente
1
¡Esto no cambia el nombre de mis archivos en absoluto!
M.Herzkamp
@ M.Herzkamp Oh, claro, las zmvbombas antes mvtienen la oportunidad de resolver las colisiones. Ok, estoy haciendo esto manualmente. Resulta que tiene exactamente la misma longitud si me salto los archivos de puntos e incluso guarda un carácter si no lo hago.
Gilles 'SO- deja de ser malvado'
1
Ahora funciona. Por cierto: incluí la penalización de espacio en un momento en que realmente tenía rencor contra los espacios;) Irónicamente, no excluí espacios cuando
publiqué
13

Bash 116 bytes, 16 espacios

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

No suprimí los errores para ganar un par de bytes más. Esto no tendrá ninguna colisión.

Si findse puede esperar GNU no posix , esto se puede acortar aún más:

Bash 110 bytes, 15 espacios

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

Eliminar espacios en lugar de reemplazarlos usa dos bytes menos:

Bash 108 bytes, 15 espacios

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

Nota: si se pueden usar pestañas en lugar de espacios, solo se necesita 1 espacio (el que está en la regla de coincidencia para la sustitución en la línea 2).

Gracias a Dennis por encontrar un error en la comilla doble (y proporcionar una solución)

pqnet
fuente
11
¿ESTÁ EL ESPACIO ADICIONAL DETRÁS DE ENCONTRARME PARA BURLARME? ;-)
M.Herzkamp
@ M.Herzkamp Pensé que era un error de copiar y pegar, pero en realidad está ahí. Supongo que gané 2 puntos más. Además, -depthen GNU puede ser reemplazado por -d, aunque se queja de que está en desuso. No sé sobre las reglas del golf, ¿puedo hacer eso?
pqnet
2
Mientras funcione, lo permito. Sin embargo, si la desaprobación se convierte en eliminación en una versión futura, es posible que
deba
2
Esto no funcionará correctamente si alguno de los nombres de archivo contiene una comilla doble. Para solucionar esto, puede usar bash -c 'B=${0##*/}...' {} \;en su lugar, que en realidad es más corto.
Dennis
3
Supongo que seré ese tipo, ¿qué pasa con la Nvariable? Nunca se define ...
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)

solo 2 espacios si usa tab para sangría :-)

Emanuele Paolini
fuente
Supongo que la mayoría de las otras respuestas podrían mejorar su puntaje al usar pestañas en lugar de espacios también.
Kasperd
Pero tu presentación usa espacios, ¿no? (+1 para el código de trabajo)
M.Herzkamp
No sé cómo atraer caracteres de tabulación en la respuesta ...
Emanuele Paolini
2
reemplazado con pestañas :-)
Emanuele Paolini
3
Qué feo ... Bueno, supongo que lo pedí :(
M.Herzkamp
5

Si el orden de los sufijos de archivos colisionados no necesita dar precedente al archivo preexistente, entonces lo siguiente funciona para mí:

bash / find / mv 84 bytes, 16 espacios

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

bash / find / mv 82 bytes, 14 espacios

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

Acurrucado &&para ahorrar dos bytes de espacio.

bash / find / mv 60 bytes, 11 espacios

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

Elimina la protección contra errores para obtener errores de mv en archivos que no tienen espacios para comenzar.

Editar: se eliminaron las citas {}según lo recordó Dennis. También se le findpermite gritar sobre portabilidad y desaprobación en la versión más corta donde mvya se está gritando sobre mover un archivo encima de sí mismo.

Edición 2: se agregó -Tal mvcomando para evitar anidar directorios en lugar de renombrar como lo señala pqnet. Se usó la expansión de llaves al costo de un personaje sobre el uso de un solo espacio.

Etan Reisner
fuente
Puede usar en -dlugar de -depthy no necesita las comillas {}.
Dennis
@Dennis Yeah. Vi la -dconversación sobre la respuesta de pqnet pero pensé que, dado que estaba silenciando los mvgritos, evitaría los findgritos. Aunque probablemente debería acortarlo por el grito. Y sí, siempre cito {}por alguna razón, aunque que no tienes que hacerlo en este caso. Fuerza de la costumbre, supongo.
Etan Reisner
1
Cuando se produce una colisión en los nombres de directorio, se colocará uno dentro de otro (y no se eliminarán espacios). Use la -Topción para mvevitar esto
pqnet
Esto funciona, y dije en el desafío que el apéndice depende de usted. +1
M.Herzkamp
4

NodeJS - 209 bytes, 3 espacios en blanco

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
fuente
No estoy familiarizado con node.js. ¿Cómo lo ejecutaría?
M.Herzkamp
Necesitará el nodo ejecutable nodejs ; guárdelo en un archivo y ejecútelonode file.js
cPu1
77
Consigo una excepción TypeError: Object #<Object> has no method 'exists'. Adivina dónde: ¡está en la línea 1! : D
M.Herzkamp
Lo he probado De todos modos, reemplacé existe con su contraparte síncrona. ¿Puedes probar ahora?
cPu1
1
Solo tengo instalada la versión 0.6.12. Ese puede ser el 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
fuente
Oops,
echaré
2
Además, los espacios se cuentan dos veces ;-)
M.Herzkamp
¿Qué quieres decir exactamente con que los espacios se cuentan dos veces?
Subbeh
1
Puede guardar muchos caracteres abreviando --backupa--b
1
Sí, ¡ahora también funciona con mi equipo de prueba! +1
M.Herzkamp
2

Bash + Perl rename64

( renamees el script de Perl en Debian y derivados, no el comando util-linux).

find . -depth -name "* *" -execdir rename 'y/ /_/' * \;
german_guy
fuente
11
¿Qué sucede si "my file.txt" y "my_file.txt" están presentes?
M.Herzkamp
1
Oh cierto ... trabajando en eso pronto
german_guy
1
*debería ser {}, tal como está, esto solo cambia el nombre de los archivos cuyo nombre aparece en el directorio actual. Esto no agrega un sufijo en caso de colisión. Podría ahorrar bastante omitiendo, -name "* *"ya que renameignora silenciosamente los archivos cuyo nombre no se transforma.
Gilles 'SO- deja de ser malvado'
2

POSIX sh+ GNU find+ GNU mv67 bytes ASCII + un espacio (literal)

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

No sé si encaja, pero con esto cualquier secuencia de espacios se elude a una sola _, me gusta de todos modos. En realidad, cualquier secuencia, excepto los espacios iniciales / finales, es decir, se trunca automáticamente (lo que también es, creo, un comportamiento beneficioso) . Gracias a Gilles por señalar esto.

Esto solo usa el separador de campo interno para separar los campos.

Es bastante ... hablador ...

...Oh hombre. Sabía que la ficha era barata, pero pensé que al menos era inteligente. Ahora llego tarde a la fiesta ...

mikeserv
fuente
Esto funciona en mi conjunto de pruebas como pretendía, pero no como lo requiere el desafío. Sin embargo, me gusta porque probablemente aprenderé algo nuevo. Supongo que tendré que leer sobre esta IFScosita mágica ...
M.Herzkamp
1
@ M.Herzkamp - ifs se comporta de manera diferente dependiendo de si está configurado en espacios en blanco o no. La mayoría de las personas lo odian porque no entienden sus dos cualidades principales: que solo opera en expansiones ( $expandno (expandido)) y lo que acabo de mencionar. Mira aquí
mikeserv
Esto no cambia el nombre de los archivos dentro de los directorios cuyos nombres contienen espacios. Una solución sería reemplazar -execcon -execdir. Otra peculiaridad de la IFSque no menciona es que los espacios finales se eliminan. Tenga en cuenta que, como otros han notado, también necesita la -Topción mv, para cuando el objetivo de una mvllamada es un directorio existente.
Gilles 'SO- deja de ser malvado'
@Gilles: preferiría usar sh -c 'mkdir -p ../newtree/"$0"; ln "$0"/* ../newtree/$0 {} \;y otros globos en un find -type dcomando para crear un árbol reflejado de enlaces duros y luego operar en ellos, pero supongo que escribir un código de golf para una operación de movimiento. Buen punto sobre los espacios iniciales / finales, aunque creo que también es un comportamiento que preferiría.
mikeserv
@Gilles, pero por cierto, no es un capricho , es un comportamiento intencionado y controlado por los estándares . La sección de división de campo se encuentra entre las pocas en la especificación de shell que no contiene las palabras no especificadas o definidas por la implementación . No existen tales garantías con zshla función integrada de zmv por ejemplo.
mikeserv
2

PHP, 147145 bytes, 2 1 espacio 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);}}}

función recursiva. Corre cons(".");

Recorrer los globresultados para la ruta dada:

  • si directorio, recurse
  • reemplazar espacios con guiones bajos
  • si las cadenas difieren
    • mientras se toma un nuevo nombre de archivo, agregue un guión bajo
    • renombrar archivo / directorio
Titus
fuente
php cambiará el nombre de los archivos en el servidor ... Ahora me pregunto cómo cambiar los nombres de archivo de un cliente cada vez que visitan su sitio: D
M.Herzkamp
1

Rubí 121

require 'find'

Find.find('.') do |file|
  if file.chomp.match(/ /)
    File.rename(file, file.gsub(/ /, '_'))
  end
end
gam3
fuente
66
¡Bienvenido a Code Golf! La idea aquí en estos desafíos de código de golf es usar la menor cantidad de caracteres. Eso significa que definitivamente puede deshacerse de las líneas y pestañas en blanco, y hacer que los nombres de las variables sean de un solo carácter, pero las personas buscan todo tipo de formas creativas para reducir el recuento de caracteres.
No es que Charles
Me aparece un error, que el directorio no está vacío: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

Pitón, 187

165, más 22 puntos de penalización por los espacios.

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 el truco \ t de Emanuele :

¡Solo un espacio en este!

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
fuente
Esto funciona para mi. +1
M.Herzkamp
elimine los espacios al comienzo de las líneas y use pestañas: no son espacios, así que cuente solo una vez
chill0r
@ chill0r Eso es lo que es la segunda versión; todos los espacios se reemplazan con pestañas, excepto uno (excepto que SO todavía los muestra como espacios).
Henry Keiter
1

LiveScript - 166

(Reemplace los espacios con pestañas).

(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) \.

Basado en la versión optimizada de nderscore de la respuesta de cPu1 .

nyuszika7h
fuente
¡Trabajos! +1 Voy a eliminar mis comentarios antes para ordenar esta publicación.
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

fuente
1
Los mismos problemas que varias otras entradas: reemplaza espacios en directorios principales y mv no puede encontrarlos. También debe cambiar la dirección de la conversión, de lo contrario, cambia el nombre de los directorios y mv no puede encontrar los archivos en su interior.
M.Herzkamp
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)

según el comentario de @ edc65

Groovy, manejar colisiones, 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()))
iniciar sesión
fuente
1
Esto no maneja colisiones.
edc65
Asegúrese de que los archivos cambien de nombre antes que sus directorios principales y que los espacios en los directorios principales no se reemplacen.
M.Herzkamp
Estoy seguro de que está bien
inicie sesión el
0

POSIX (Probado en zsh) + comandos básicos de 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
fuente
@ M.Herzkamp fijo.
LinGeek
Varias cosas: ¿cuál es la función de exportar IFS y c en ls -cR? ¿Y qué versión de mv necesita para la opción de respuesta? (Tengo 8.13, y no reconoce la opción). También para obtener una mejor puntuación, debe abreviar los nombres de sus variables.
M.Herzkamp
La c reemplaza espacios con líneas nuevas. El IFS detiene los espacios como separadores. La respuesta es de versiones anteriores y está a punto de ser reparada.
LinGeek
1
¿Te estás perdiendo un segundo mv en la línea 5? Y creo que un eco en esa línea está mal.
M.Herzkamp
1
$(ls -CR)Es completamente falso. La -copción es inútil, y -Robtiene archivos sin su directorio, lo cual no tiene sentido. Su arquitectura fundamentalmente no manejará nombres de archivos que contengan nuevas líneas. Necesita set -fo de lo contrario los nombres de archivo que contienen comodines explotarán. exportes inútil Puedo ver vagamente lo que intentas hacer para uniquificar archivos, pero la tubería está mal.
Gilles 'SO- deja de ser malvado'