Search code examples
bashapplescriptbatch-rename

Batch renaming a single file (always same file name) within a folder to the folder name (100s of folders)


I'm currently trying to convert a file that is contained within my Ulysses export (= a bunch of textbundle files). So, once I export all my sheets from my Ulysses (Markdown App), I'll have the following files (slightly over 700 of them) in one folder containing e.g.:

  • Exmaple File.textbundle
  • Some random name.textbundle Another random
  • Another random name.text bundle
  • ...

This is what one of these textbundle files looks like

This is what one of these textbundle files looks like

Where I need help: I'm struggling a bit do create an Automator script, bash or whatever is easier or gets the job done (I can't program) and does the following:

Removes the extension of the textbundle file, so it becomes a folder

Once the extension is removed, the textbundle file becomes a normal folder

Once the extension is removed, the textbundle file becomes a normal folder

  1. Rename the text.md file to the folder name (e.g. Exmaple File.md based on the example from above). Keep in mind: All .md files, no matter what textbundle file is being processed, will always have the same - text.md.filename, Not sure why that's the case, but it should make it easier to script, I hope.

  2. Reverse step 1 and add the extension .textbundle back to the folder so it becomes a textbundle file again

  3. Move on to the next file e.g. Some random name.textbundle and repeat steps 1-3.

Essentially, I'll have the hundreds of textbundles files and want to have an automaton script that goes through the process (1-3), renaming the text.md contained in each textbundle file so I can cleanly import these into another program.

Anyone know how the code would need to look like?


Solution

  • Suppose you have a folder with content along these lines:

    $ tree -N
    .
    |-- name 1.textbundle
    |   `-- text.md
    |-- name 2.something
    |   `-- text.md
    |-- name 3.textbundle
    |   `-- text.md
    |-- name4.textbundle
    |   `-- text.md
    `-- name5.textbundle
        `-- text.md
    
    5 directories, 5 files
    

    Then a script like the following, could be used to rename those text.md files within those *.textbundle directories, along the lines of how you suggested:

    #!/bin/bash
    
    if [ $# -ne 1 ]; then
        echo "Usage: $0 <directory>" 1>&2
        exit 1
    fi
    if [ ! -d "$1" ]; then
        echo "$0: '$1' is not a directory" 1>&2
        exit 1
    fi
    
    find . -type d -name '*.textbundle' | \
    while read dirname; do
        base=$(basename "$dirname" .textbundle)
        echo "$dirname => $base:"
        pushd "$dirname" > /dev/null
        if [ -e "text.md" ]; then
            mv "text.md" "$base.md"
            echo "    Moved $dirname/text.md => $dirname/$base.md"
        fi
        popd > /dev/null
    done
    

    It requires one parameter, the base directory of where you keep all these *.textbundle directories. So if that is in ~/Documents/ for instance, you would run it like <scriptname> ~/Documents.

    Running it from the start the above example directory, it would look like this:

    $ <script-name> .
    ./name 1.textbundle => name 1:
        Moved ./name 1.textbundle/text.md => ./name 1.textbundle/name 1.md
    ./name5.textbundle => name5:
        Moved ./name5.textbundle/text.md => ./name5.textbundle/name5.md
    ./name 3.textbundle => name 3:
        Moved ./name 3.textbundle/text.md => ./name 3.textbundle/name 3.md
    ./name4.textbundle => name4:
        Moved ./name4.textbundle/text.md => ./name4.textbundle/name4.md
    

    If you run it again, it won't do any accidental damage, it just won't rename any files:

    $ <script-name> .
    ./name 1.textbundle => name 1:
    ./name5.textbundle => name5:
    ./name 3.textbundle => name 3:
    ./name4.textbundle => name4:
    

    The following script moves all those files back, in case you change your mind:

    #!/bin/bash
    
    if [ $# -ne 1 ]; then
        echo "Usage: $0 <directory>" 1>&2
        exit 1
    fi
    if [ ! -d "$1" ]; then
        echo "$0: '$1' is not a directory" 1>&2
        exit 1
    fi
    
    find . -type d -name '*.textbundle' | \
    while read dirname; do
        base=$(basename "$dirname" .textbundle)
        echo "$dirname => $base:"
        pushd "$dirname" > /dev/null
        if [ -e "$base.md" ]; then
            mv "$base.md" "text.md"
            echo "    Moved $dirname/$base.md => $dirname/text.md"
        fi
        popd > /dev/null
    done
    

    It too takes the base directory as the (only) parameter.

    $ <revert-script-name> .
    ./name 1.textbundle => name 1:
        Moved ./name 1.textbundle/name 1.md => ./name 1.textbundle/text.md
    ./name5.textbundle => name5:
        Moved ./name5.textbundle/name5.md => ./name5.textbundle/text.md
    ./name 3.textbundle => name 3:
        Moved ./name 3.textbundle/name 3.md => ./name 3.textbundle/text.md
    ./name4.textbundle => name4:
        Moved ./name4.textbundle/name4.md => ./name4.textbundle/text.md
    

    And it too will skip the renaming if it doesn't find an appropriate *.md file.

    Both scripts use find to find directory names of the required name pattern, and then visit each one of them and look for an appropriate *.md file, renaming it when found. basename is used to take the directory name and simplify it the last part of the path, minus the .textbundle extension.