Search code examples
bashterminalrenamemv

Mass rename folders and their files in bash/terminal


I am trying to mass rename folders and their respective files by use of the command "mv" but its not going very well, I would like the folders to be renamed into:

1/
2/
3/

etc.

The files in each folder should in this process also be renamed while keeping their file extension.

1.png
2.gif
3.jpg

etc

Very thankful for your help


Solution

  • EDIT: Better approach with a function rather than recursive calls to the same script.

    Here's hoping I got all corner cases. This descends into directories recursively to handle deeply nested directory trees.

    Caveat: Because the script takes care to not overwrite existing files, gaps may appear in the numbering in some corner cases -- if there is a file 0.txt in a directory and the first file that is handled in that directory is a .txt file, it will be moved to 1.txt. This also happens if the first file that is handled is 0.txt, so running the script twice will change the numbering, and running it again will change it back.

    So here's the code:

    #!/bin/bash
    
    handle_directory() {
        local counter=0
    
        for i in *; do
            # if the file is a directory (but not a symlink),
            #  handle it before moving
            if [ -d  "$i" ] && ! [ -h "$i" ]; then
                cd "$i"
                handle_directory
                cd ..
            fi
    
            # extract suffix
            suffix="${i##*.}"
    
            if [ "$suffix" != "$i" ]; then
                extension=".$suffix"
            else
                # If there is no filename extension, the counter
                # is the whole filename. Without this, we'd get
                # 0.Makefile and suchlike.
                extension=""
            fi
    
            # find a filename that isn't already taken
            # this may lead to gaps in the numbering.
            while dest="$counter$extension" && [ -e "$dest" ]; do
                let ++counter
            done
    
            echo mv "$i" "$dest"
            let ++counter
        done
    }
    
    # if a parameter was given, go there to handle it.
    # otherwise handle the local directory. 
    if ! [ -z "$1" ] && ! cd "$1"; then
        echo "Could not chdir to directory $1"
        exit -1
    fi
    
    handle_directory
    

    The general idea is a depth-first search of the directory tree in question. Like any tree, the directory tree is best handled recursively, and the function essentially boils down to: Walk through all things in this directory, if they are a directory, descend and handle it, then find an appropriate file name and rename the thing whether it is a directory or not.

    Things used:

    local counter=0 # declares a function-local variable counter and initializes it
                    # to 0. crucially, that it is local means that every invocation
                    # of handle_directory has its own counter.
    [ -d "$i" ]     # tests if "$i" is a directory
    [ -h "$i" ]     # tests if "$i" is a symlink
    ! [ ... ]       # negates the test. ! [ -h "$i" ] is true if "$i" is NOT a symlink
    "${i##*.}"      # a bashism that cuts off the longest prefix that matches the pattern
    [ -e "$dest" ]  # tests if "$dest" exists
    $1              # the first parameter with which the script is called
    [ -z "$1" ]     # tests if "$1" is an empty string
    ! cd "$1"       # descends into the directory "$1". true if that failed.
    

    Manpages to read to further understanding:

    man test
    man bash # that's the big one.