# I HATE spaces in file names

It is simple. I cannot stand when people use spaces when naming files. It sometimes wrecks console commands and makes the output of ls ugly.

The challenge is to write a program (only ascii characters) which

1. renames all files (including directories) in the current directory to versions with spaces removed or replaced by '_'
2. on collision, you need to append a unique identifier (up to you)
3. descends recursively into all subdirectories

You can assume UNIX-style path names. Who would need this program on a Windows machine anyways?

This is code golf, the shortest program wins (#ascii characters). Since I hate spaces so much, each space has to be counted twice.

Please provide your language, score, program and a short description of how to run it.

The program must compile and execute with reasonable effort on my linux machine.

EDIT: As Etan requested a file structure for testing, here is the script I currently use to create a suitable file tree:

#!/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

• This is begging for a solution made without ascii chars. Aug 6, 2014 at 15:44
• Now I want to learn Whitespace Aug 6, 2014 at 18:03
• @BrunoJ doing this in Whitespace would first require you to develop a file access system in WS. I think that would be more challenging than the actual challenge. Aug 7, 2014 at 6:40
• Waiting for someone to post a C/C++ solution so I can steal it, compile, post in hex as x86 machine code with ZERO spaces! [or maybe base64] Aug 7, 2014 at 13:35
• I hate underscores in filenames. Use dashes. Aug 7, 2014 at 17:07

## Bash 116 bytes, 16 spaces

find . -depth -exec bash -c 'B=${0##*/} M="${0%/*}/${B// /_}" while [ -e "$M" ]
do M=$M. done mv "$0" "$M"' {} \;  I didn't suppress errors to gain a couple more bytes. This will not have any collisions. If non-posix GNU find can be expected, this can be shortened further: ## Bash 110 bytes, 15 spaces find -d -exec bash -c 'B=${0##*/}
M="${0%/*}/${B// /_}"
while [ -e "$M" ] do M=$M.
done
mv "$0" "$M"' {} \;


Removing spaces instead of replacing them uses two less bytes:

## Bash 108 bytes, 15 spaces

find -d -exec bash -c 'B=${0##*/} M="${0%/*}/${B// }" while [ -e "$M" ]
do M=$M. done mv "$0" "$M"' {} \;  Note: if tabs can be used instead of spaces, only 1 space is needed (the one in the match rule for substitution at line 2). Thanks to Dennis for finding bug on double quote (and providing solution) • IS THE EXTRA SPACE BEHIND find THERE TO MOCK ME??? ;-) Aug 6, 2014 at 22:01 • @M.Herzkamp I though it was a copy and paste error, but it is actually there. Guess I gained 2 more points. Also, -depth in GNU can be replaced by -d, though it complains that it is deprecated. I don't know about the rules of golf, can I do that? Aug 6, 2014 at 22:16 • As long as it works, I allow it. Should the deprecation become removal in a future version though, I might have to come back to this answer and downvote it for not being correct ;-) Aug 6, 2014 at 22:21 • This won't work properly if any of the filenames contains a double quote. To fix this, you can use bash -c 'B=${0##*/}...' {} \; instead, which is actually shorter. Aug 7, 2014 at 14:53
• I guess I will be that guy, what is up with N variable? It is never defined... Aug 9, 2014 at 5:04

## Zsh + GNU coreutils — 48 bytes (1 space)

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


It's weird that you hate (ASCII) spaces but are fine with tabs and newlines, but I guess it takes all kinds.

zmv solves a lot of file renaming problems concisely (and only slightly obscurely). However, it insists on the targets being unique; while you can easily add unique suffixes, adding a suffix only if it would be needed pretty much requires re-doing all the work. So instead I loop manually and rely on GNU mv to append a unique identifier in case of collision (--backup option, plus --no-target-directory in case a target is an existing directory, as otherwise mv would move the source inside that directory).

(od) is a glob qualifier to sort the output with directories appearing after their content (like find's -depth). D includes dot files in the glob. :h and :t are history modifiers similar to dirname and basename.

mv complains that it's called to rename files to themselves, because the glob includes file names without spaces. C'est la vie.

Ungolfed version:

for x in **/*\ *(Dod); do
mv --no-target-directory --backup=numbered $x${x:h}/${${x:t}// /}
done

• this does not rename my files at all! Aug 11, 2014 at 11:56
• @M.Herzkamp Oh, right, zmv bombs out before mv has a chance to sort out collisions. Ok, I'm doing this manually. Turns out to be exactly the same length if I skip dot files and even saves a character if I don't. Aug 11, 2014 at 16:15
• Now it's working. Btw: I included the space penalty at a time where I really had a grudge against spaces ;) Ironically, I did not exclude spaces when I posted the challenge :P Aug 12, 2014 at 7:36

# 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)


only 2 spaces if you use tab for indentation :-)

• I guess most of the other answers could improve their score by using tabs instead of spaces as well. Aug 7, 2014 at 8:14
• But your submission uses spaces doesn't it? (+1 for working code) Aug 7, 2014 at 9:40
• I don't know how to att tab characters in the answer... Aug 7, 2014 at 10:11
• replaced with tabs :-) Aug 7, 2014 at 11:14
• How ugly... Well, I guess I asked for it :( Aug 7, 2014 at 11:33

If the order of collided file suffixes does not need to give precedent to the pre-existing file then the following works for me:

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


Cuddled && to save two space bytes.

## bash/find/mv 60 bytes, 11 spaces

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


Drops error protection so it gets errors from mv on files which have no spaces to start with.

Edit: Dropped the quotes from {} as reminded by Dennis. Also allowed find to scream about portability and deprecation in the shortest version where mv is already screaming about moving a file on top of itself.

Edit 2: Added -T to mv command to avoid nesting directories instead of renaming as pointed out by pqnet. Used brace expansion at cost of one character over just using one space.

• You can use -d instead of -depth and you don't need the quotes around {}. Aug 8, 2014 at 19:21
• @Dennis Yeah. I saw the -d conversation on pqnet's answer but figured since I was silencing the mv screaming I'd avoid the find screaming. Though I should probably shorten it for the screaming one. And yeah, I always quote {} for some reason even though I know you don't have to in this case. Force of habit I guess. Aug 8, 2014 at 19:30
• When collision happen on directory names, it will put one into another (and not strip spaces). Use -T option to mv to avoid this Aug 8, 2014 at 22:53
• This works, and I said in the challenge that the appendix is up to you. +1 Aug 12, 2014 at 7:48

## NodeJS – 209 bytes, 3 Whitespaces

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('.');

• I am not familiar with node.js. How would I run it? Aug 7, 2014 at 12:22
• You'll need the Node executable nodejs; save it in a file and run node file.js Aug 7, 2014 at 12:27
• I get an exception TypeError: Object #<Object> has no method 'exists'. Guess where: it's in line 1! :D Aug 7, 2014 at 12:45
• I have tested it. Anyway, I replaced exists with its synchronous counterpart. Can you try now? Aug 7, 2014 at 12:53
• I have only version 0.6.12 installed. That may be the problem. Aug 7, 2014 at 13:26

## POSIX sh + GNU find + GNU mv 67 ASCII bytes + one (literal) space

find    -d  -exec   sh  -cf 'IFS=\ ;IFS=_   set $0;mv --b=t "$0"    "$*"' {} \;  I don't know if it fits, but with this any sequence of spaces is elided to a single _ - I like it anyway. Actually any sequence but leading/trailing spaces that is - those are automatically truncated (which is also, I think, a beneficial behavior). Thanks to Gilles for pointing this out. This just uses the internal field separator to separate fields. It's fairly... chatty... ...oh man. I knew the tab thing was cheap, but I thought it was at least clever. Now I'm just late to the party... • This works on my test set as you intended, but not as the challenge requires. I like it, though, because I will probably learn something new. I guess I will have to read up on this IFS magic thingy... Aug 11, 2014 at 11:44 • @M.Herzkamp - ifs behaves differently depending on whether it is set to whitespace or not. Most people hate it because they do not understand its two primary qualities - that it only operates on expansions ($expand not (ex pand)) and the ifsws thing just mentioned. Look here Aug 11, 2014 at 12:08
• This doesn't rename files inside directories whose names contain spaces. A fix would be to replace -exec with -execdir. Ånother quirk of IFS that you aren't mentioning is that trailing spaces are deleted. Note that as others have noticed you need the -T option to mv as well, for when the target of an mv call is an existing directory. Aug 11, 2014 at 16:26

## Ruby 194

require'find'
require'fileutils'
Find.find(?.).sort{|a,b| b.length<=>a.length}.each {|f|
if f.match(/ /)
o=f.tr(' ',?_)
begin
raise if File.exist? o
FileUtils.mv f,o
rescue
o+=?_
retry
end
end}


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

• Same problems as several other entries: You replace spaces in parent directories and mv cannot find them. Also you must change direction of traversion, otherwise you rename directories and mv cannot find the files inside. Aug 8, 2014 at 8:59

Groovy, 139 characters

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)


according to @edc65 comment

Groovy, handle collisions, 259 characters

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

• This does not handle collisions. Aug 13, 2014 at 13:00
• Make sure that files are renamed before their parent directories are, and that the spaces in parent directories are not replaced. Aug 14, 2014 at 11:49
• I'm sure it's ok Aug 14, 2014 at 15:36