I made a bash script to bulk rename files!

“But… why?”

I know the default file manager comes with a bulk rename tool, but it was bugging me just how slow it would get sometimes compared to if I used Nemo instead. Except I ran into an issue - I can’t bulk rename files/folders the same way. There is technically a way to (sort of) get Nemo to use Thunar’s bulk rename, but it just felt too clunky.

DISCLAIMER: All I did was constantly prompt ChatGPT with different constraints and requirements until it spit out code that worked for me

As of posting this, this is what the script features:

  • Regardless of case sensitivity, identify any string in a file or folder name (doesn’t discriminate and will operate regardless of file or folder) and replace said string with whatever string you input. I imagine you can have it only rename folders, or only rename files, but for my use case I prefer it rename anything in said directory

  • Will rename parts of words (ex. if there is a file named sledgehammer.jpg and the text you want to replace is sledge, sledgehammer.jpg will be renamed to [new text]hammer.jpg). This shouldn’t be too big of an issue, as in most cases of bulk renaming, the files being renamed will have a string of characters in common, which ends up being the target text that gets replaced

  • If you hit enter without adding text, it will simply remove the text

  • If the file name contained spaces, replacing a word won’t replace the space between said word and the next word in a file/folder’s name

  • You can choose to append text to the beginning or end of said filenames (this can technically just be done with the replace text option, but I realized that too late and it’s easier to just have that as an option instead of saying “well it’s intuitive if you play around with it”)

What the script cannot do:

  • Currently there is no option for a user to select specific files in a directory. In rare cases, you may have to move specific files into a separate folder and set that as the directory for the script to run with. I was messing with something like this, but it got me nowhere so I just scrapped the whole idea for now:
rename_specific_files() {
    local files=("$@")

    for file in "${files[@]}"; do
        if [ -e "$file" ]; then
            # Check if filename has spaces
            if [[ $file == *" "* ]]; then
                # Reverse the order of the filename after the space
                dir=$(dirname "$file")
                filename=$(basename "$file")
                first_part="${filename%% *}"
                second_part="${filename#* }"
                new_name="$dir/$second_part $first_part"
            else
                new_name="$file"
            fi
            read -rp "Enter the new name for \"$file\": " -i "$new_name" new_name
            mv "$file" "$new_name"
        else
            echo "File \"$file\" not found."
        fi
    done
}

The issue being that if the selected file(s) have spaces in the filenames, it just doesn’t handle it right. I don’t have enough experience coding to understand how to fix this on my own. If anyone wants to add to this, feel free. It would definitely make the script a bit more versatile

Anyways, without further ado, here is the full bash script as it stands:

#!/bin/bash

# Function to add text to filenames
add_text() {
    local dir=$1
    local text=$2
    local position=$3

    cd "$dir" || exit 1

    for entry in *; do
        if [ -d "$entry" ]; then
            mv "$entry" "$text$entry"
        elif [ -f "$entry" ]; then
            if [ "$position" == "beginning" ]; then
                mv "$entry" "$text$entry"
            elif [ "$position" == "end" ]; then
                extension="${entry##*.}"
                filename="${entry%.*}"
                mv "$entry" "$filename$text.$extension"
            fi
        fi
    done

    cd - > /dev/null || exit 1
}

# Function to replace text in filenames
replace_text() {
    local dir=$1
    local old_text=$2
    local new_text=$3

    cd "$dir" || exit 1

    for entry in *; do
        if [ -d "$entry" ]; then
            new_name=$(echo "$entry" | sed "s/$old_text/$new_text/gI")
            mv "$entry" "$new_name"
        elif [ -f "$entry" ]; then
            new_name=$(echo "$entry" | sed "s/$old_text/$new_text/gI")
            mv "$entry" "$new_name"
        fi
    done

    cd - > /dev/null || exit 1
}

# Main script
echo "Bulk renaming script"

# Choose directory through file-picker
directory=$(zenity --file-selection --directory --title="Select Directory")
if [ $? -ne 0 ]; then
    echo "No directory selected. Exiting..."
    exit 1
fi

# Change to the selected directory
cd "$directory" || exit 1

echo "1. Add text to filenames"
echo "2. Replace text in filenames"
read -rp "Choose an option (1/2): " option

case $option in
    1)
        read -rp "Enter the text to add: " text
        read -rp "Should it be appended to the beginning or end? (beginning/end): " position
        echo "Adding \"$text\" to filenames and folder names..."
        add_text "$(pwd)" "$text" "$position"
        ;;
    2)
        read -rp "Enter the text to replace: " old_text
        read -rp "Enter the new text: " new_text
        echo "Replacing \"$old_text\" with \"$new_text\" in filenames and folder names..."
        replace_text "$(pwd)" "$old_text" "$new_text"
        ;;
    *)
        echo "Invalid option, exiting..."
        exit 1
        ;;
esac

echo "Done"

SECOND DISCLAIMER: While this does technically solve my stated problem, there is a good chance that a lot of this code is un-optimized and hacky. This is a glaring downside when using GPT to write code and make scripts. While it doesn’t bother me personally as long as it gets the job done, I would recommend only using this as a reference if you have more experience than me with bash scripts.

A lot of my scripts will loosely follow this schematic: prompt user with options to narrow down exactly what functions to use depending on what the user wants (within the realm of the script’s capability), and only run code based on the responses to those prompts

1 Like

Maybe you could join this thread

:wink:

5 Likes

will post this there as well!

The way I do this task is this:

The package renameutils from the Arch extra repository contains a useful tool called qmv. I have an alias rename=qmv --format=do

So, I just position myself in some directory and run rename. This opens my default text editor with a list of all files and directories in the current directory, one on each line. Then, whatever changes I make to this document, the changes are reflected in the new names of files.

Thus, I can use advanced features of my text editor to do string substitution, or even feed the list of files as stdin to an external command, like sed or any script. It’s really the most flexible and powerful way to rename files, I know of.

5 Likes

yeah, for my use case i can just as easily substitute a tool like this in. but to me it’s efficient using built-in tools with a bash script. if i really had it my way, there’d just be an option to bulk rename whatever files i selected in Nemo’s right-click menu

edit - or at least with a more intuitive UI

1 Like

You can do this btw

To add a script to the right-click menu in Nemo, you can follow these steps:

  1. Create a script file and save it in the .local/share/nemo/scripts directory
  2. Make the script executable by running chmod +x /path/to/your/script.sh
  3. Restart Nemo to see the script in the right-click menu

To pass the file path or the current working directory to your script, you can use the $NA_FILE_PATH and $NA_WORKSPACE_URI variables, respectively. These variables are provided by Nemo and contain the path to the selected file or the current workspace URI, respectively. For example, if you have a script that takes a file path as an argument, you can modify it to use the $NA_FILE_PATH variable as follows:

#!/bin/bash

# Use the $NA_FILE_PATH variable to get the path to the selected file
if [ -n "$NA_FILE_PATH" ]; then
  file_path="$NA_FILE_PATH"
else
  # If no file is selected, prompt the user to enter a file path
  read -p "Enter the file path: " file_path
fi

# Do something with the file path
echo "File path: $file_path"

With this modification, your script will be able to handle both cases where a file is selected and where no file is selected.

3 Likes