How to rename files with spaces in file and directory names

Hello,

I received a big folder with a lot of files and subfolders that have spaces in their name.

I am trying to write a function that replaces the spaces with underscore characters.

The following code does NOT work:

const BASEDIR="./input"

function rename_files(files)
    for file in files
        new_file=replace(file, " " => "_")
        if isfile(file)
            if file != new_file
                old_file=replace(file, " " => "\\ ")
                println(old_file)
                println("  ", new_file)
                mv(old_file, new_file)
            end

        else
            println("===>>> DIR")
        end

    end
end

function fix_names()
    for (root, dirs, files) in walkdir(BASEDIR)
        rename_files(joinpath.(root, files)) # files is a Vector{String}, can be empty
    end
end

fix_names()

The problem is that mv does not find the file it shall rename, because the name includes spaces.

Now I try to replace " " with "\ " in the old_file name, but that also doesn’t work because the replace function gives an error in that case:

julia> file="hund hase"
"hund hase"

julia> replace(file, " " => "\ ")
ERROR: syntax: invalid escape sequence
Stacktrace:
 [1] top-level scope
   @ none:1

And using a double backslash also does not work.

Any idea?

Shouldn’t this just work without escaping:

julia> mv(file, new_file)
"hund_hase"

shell> ls hund_hase
hund_hase

Yes, spaces should not matter for mv (and should not be escaped).

@ufechner7 you should do mv(file, ...) (no need for old_file). But then there is another problem: the code tries to rename dir 1/file 1 to dir_1/file_1. This is not possible since dir_1 doesn’t exist.

So I would give root and files as two parameters to rename_files.

3 Likes

The following code now works:

const BASEDIR="./input"

function rename_file_names(root, files)
    for file in files
        new_file=replace(file, " " => "_")
        if isfile(joinpath(root,file))
            if file != new_file
                mv(joinpath(root,file), joinpath(root,new_file))
            end
        else
            println("===>>> DIR")
        end

    end
end

function rename_dir_names(root, files)
    new_dir=replace(root, " " => "_")
    if isdir(root)
        old_dir = root
    else
        old_dir = replace(joinpath(splitpath(root)[1:end-1]...), " " => "_")
        old_dir = joinpath(old_dir, splitpath(root)[end])
    end
    if isdir(old_dir)
        if old_dir != new_dir
            mv(old_dir, new_dir)
        end
    else
        println("===>>> ERROR")
    end
end

function fix_names()
    for (root, dirs, files) in walkdir(BASEDIR)
        rename_file_names(root, files) # files is a Vector{String}, can be empty
    end   
    for (root, dirs, files) in collect(walkdir(BASEDIR))
        rename_dir_names(root, files) # files is a Vector{String}, can be empty
    end
end

fix_names()

But it is not generic (will fail for directories nested more than 2 levels).

It is ever so slightly faster (but more ‘correct’ imho) to use Chars instead:

new_file=replace(file, ' '=> '_')
3 Likes

I think it can be made generic easily by using the topdown option of walkdir, so that children are iterated before their parents. The following works according to my tests (on Linux):

function fix_names(root, names)
    for name in names
        mv(joinpath(root, name), joinpath(root, replace(name, ' ' => '_')))
   end
end

for (root, dirs, files) in walkdir("input", topdown=false)
    fix_names(root, [dirs; files])
end

This code misses your check for collisions though. BTW I think that check should normalize names, at least to avoid uppercase-lowercase collisions on case-insensitive filesystems.

1 Like

Your code is so elegant!

But I need the check to make sure it also works if some of the files do NOT contain any space in the filename. This is my code now:

const BASEDIR="./input"
const DOCDIR="./doc"

function fix_names(root, names)
    for name in names
        old_name = joinpath(root, name)
        new_name = joinpath(root, replace(name, ' ' => '_'))
        if old_name != new_name
            mv(old_name, new_name)
        end
    end
end

function fix_names(dir)
    for (root, dirs, files) in walkdir(dir, topdown=false)
        fix_names(root, [dirs; files])
    end
end

fix_names(BASEDIR)
fix_names(DOCDIR)

Case collisions cannot happen. You will only have a collision if there is no space in the filename.

Thank you very much!

2 Likes

Oh indeed in my test case every file and directory had spaces :slight_smile:

You can still have collisions if a directory contains both file 1 and file_1: if you rename the first file it will overwrite the second one. And on Windows and MacOS, file_1 and File_1 are the same file so renaming file 1 can collide with File_1…

Adding as a way to rename file in same directory

#Jun20,2021 : Rename files in one single folder
const BASEDIR=“/home/hillel/workjulspace/testdata/B”
files = readdir(BASEDIR, join=true, sort = true)
filenames = readdir(BASEDIR,sort = true)
#println(files)
#println(filenames)
#println(files)
img_files = filter(endswith(“.png”),files)
for (i,file) in enumerate(img_files)
#println(i)
#println(file)
mv(file,“/home/hillel/workjulspace/testdata/B/BB_$(filenames[i])”)
end

#Jun20,2021 : Rename files in one single folder

const BASEDIR="/home/hillel/workjulspace/testdata/B"
files = readdir(BASEDIR, join=true, sort = true)
filenames = readdir(BASEDIR,sort = true)
#println(files)
#println(filenames)
#println(files)
img_files = filter(endswith(".png"),files)
for (i,file) in enumerate(img_files)
    #println(i)
    #println(file)
    mv(file,"/home/hillel/workjulspace/testdata/B/BB_$(filenames[i])")
end
1 Like

Please put triple quotes around you code examples, like this:
```julia

```