Bulk File Rename

Hello

I would like to rename all files in a database with a prefix of “YYYYMMDD” to a prefix of “YYYY-MM-DD”.

Any help would be much appreciated.

–Jonahtan

This works on a selection of files that begin with a string of 8 numbers (regardless if it’s actually yyyymmdd). It will treat those 8 numbers as yyyy-mm-dd (or yyyy-dd-mm, depending on how you view them)…

  1. Select some files to modify the names.
  2. Choose Scripts > Rename > Rename using RegEx.
    If this isn’t available, you can find it in Scripts > More Scripts, on the second page of available scripts to install.
  3. Paste b([0-9]{2})([0-9]{2})(.*)[/b] as the source pattern.
  4. Paste \1-\2-\3\4 as the destination pattern.
  5. Repeat as needed.

PS: There is no “undo” for this operation. Test this out on some files and groups first.

Great - thanks so much.

Is there anyway to do this on all files in a database without having to go in to all the nested groups and select files manually?

No.
You need to make a selection of some sort, and I certainly would not advocate doing it “on all the files in a database”… unless there were very few files. You shouldn’t attempt to do such sweeping changes unless you are working with a very strictly conforming dataset.

You could create a smart group (see Data > New > Smart Group) like this…

Given Jim’s important caution about over-automating anything that changes data, you can at least:

  • Define a reporting or modifying function which applies to one DT Item, and
  • use a generic higher-order function to apply that one-item function recursively to a selected item and all its descendants.

The fmap for applying some other function recursively could be written in JavaScript for Automation (and specialised for DEVONthink) as:

Javascript for Automation

// A generic function which applies some other function
// to a DT Item, and also to each of its descendants,
// returning a tree structure in which each node contains
// the results of that function application.

// fmapPureDT :: (DTItem -> a) -> DTItem  -> Tree a
const fmapPureDT = (f, item) => {
    const go = x => Node(
        f(x),
        x.children()
        .map(go)
    );
    return go(item);
};

or in Applescript as:

-- A generic function which applies some other function
-- to a DT Item, and also to each of its descendants,
-- returning a tree structure in which each node contains
-- the results of that function application.

-- fmapPureDT :: (DTItem -> a) -> DTItem  -> Tree a
on fmapPureDT(fn, dtItem)
    using terms from application "DEVONthink Pro"
        script go
            property f : mReturn(fn)'s |λ|
            on |λ|(x)
                Node(f(x), my map(go, children of x))
            end |λ|
        end script
        
        go's |λ|(dtItem)
    end using terms from
end fmapPureDT

A full example which just adds a tag to the selected item, and to all of its descendants, might be (first in JXA and then in Applescript):

Javascript for Automation

(() => {
    'use strict';

    // valueOrTransform :: DTItem -> IO Dict
    function valueOrTransform(item) {

        // A small change, like adding a tag
        item.tags = item.tags().concat('toRead');

        // and a returned report.
        return {
            title: item.name(),
            tags: item.tags()
        };
    }

    // MAIN ---------------------------------------------------
    const main = () => {
        const
            dt = Application('DevonThink Pro'),
            seln = dt.selection();

        // Apply fmapPureDT to the first selected item,
        // and let it recurse through all descendants.

        return seln.length > 0 ? (
            fmapPureDT(valueOrTransform, seln[0])
        ) : [];
    };

    // A generic function which applies some other function
    // to a DT Item, and also to each of its descendants,
    // returning a tree structure in which each node contains
    // the results of that function application.

    // fmapPureDT :: (DTItem -> a) -> DTItem  -> Tree a
    const fmapPureDT = (f, item) => {
        const go = x => Node(
            f(x),
            x.children()
            .map(go)
        );
        return go(item);
    };

    // Node :: a -> [Tree a] -> Tree a
    const Node = (v, xs) => ({
        type: 'Node',
        root: v,
        nest: xs || []
    });

    // MAIN ---
    return main();
})();

Applescript

-- valueOrTransform :: DTItem -> IO Dict
on valueOrTransform(x)
    using terms from application "DEVONthink Pro"
        
        -- A small change, like adding a tag
        set tags of x to (tags of x) & "toRead"
        
        -- and a returned report.
        {title:name of x, tags:tags of x}
        
    end using terms from
end valueOrTransform


on run
    tell application "DEVONthink Pro"
        
        -- Apply a function (thru fmapPureDT) to the first 
        -- selected item, and let fmapPureDT recurse 
        -- with it through all descendants.
        
        set seln to selection
        if length of seln > 0 then
            my fmapPureDT(valueOrTransform, item 1 of seln)
        else
            seln
        end if
    end tell
end run


-- A generic function which applies some other function
-- to a DT Item, and also to each of its descendants,
-- returning a tree structure in which each node contains
-- the results of that function application.

-- fmapPureDT :: (DTItem -> a) -> DTItem  -> Tree a
on fmapPureDT(fn, dtItem)
    using terms from application "DEVONthink Pro"
        script go
            property f : mReturn(fn)'s |λ|
            on |λ|(x)
                Node(f(x), my map(go, children of x))
            end |λ|
        end script
        
        go's |λ|(dtItem)
    end using terms from
end fmapPureDT

-- Node :: a -> [Tree a] -> Tree a
on Node(v, xs)
    {type:"Node", root:v, nest:xs}
end Node

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

I have been using this - is there anyway to make a menu item (or keyboard shortcut) to do this without having to fill in the source and destination patterns each time?

Thanks

Hi - this RegEx seems to have stopped working with DT3 - I get the following error:
“sed: 1: “s/b([0-9]{2})([0-9]{2}) …”: \4 not defined in the RE”

Any suggestions would be very gratefully received as I use this frequently as I come across groups with old files in.

Thanks

I fixed it - it was the “b” and “[/b]” not sure what they were doing there.
My working regex:
([0-9]{4})([0-9]{2})([0-9]{2})(.*)

replace with: \1-\2-\3\4

Interesting. That was a code to embolden that phrase. Not sure what happened on the forum post.