Recreate the vidir utility in your favorite programming language.

Vidir is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Vidir is a bulk file renaming utility included in the moreutils package available for Linux and Linux-like operating systems.


Basic operation involves:

  • Reading the file names from a directory into a text file.
  • Editing the text file with a third party editor to make bulk changes to file names (modify,delete).
  • Using the edited text file to then apply the desired changes to the file system.


In general, the filenames are typically written to the text file, one to a line with an identifying label (a unique integer in the originals case). The text file is edited as desired; file names changed, files moved, files removed, files added. Then the edited text file is used to apply the changes to the file system.

It is not required to support multiple operating systems. Make a note of which one(s) are supported.


There are many possible ways to enhance the utility but that is the basic functionality.

Some possible enhancements:

  • Operate on directories other than the current directory.
  • Recursive directory search.
  • File input filtering.
  • File/Directory permissions detection.
  • Allow creation of files / directories.
  • Verbose operation.
  • Text editor selectable from the command line.
  • First party text editing.
  • Support multiple operating systems


At a minimum support file name modification and file removal in the current directory. Add as many enhancements as desired.


See also


Should work on Windows and Linux.

' Function to get all files in the current directory
Function GetFiles() As String
    Dim As String filename, files = ""
    Dim As Integer filenum = 1
    
    filename = Dir("*")
    While Len(filename) > 0
        files += filenum & " " & filename & !"\n"
        filenum += 1
        filename = Dir()
    Wend
    
    Return files
End Function

' Function to write files to a temporary file
Sub WriteToFile(files As String)
    Dim As Integer ff = Freefile
    Open "temp_vidir.txt" For Output As #ff
    Print #ff, files
    Close #ff
End Sub

' Function to read the edited file
Function ReadEditedFile() As String
    Dim As Integer ff = Freefile
    Dim As String linea, content = ""
    
    Open "temp_vidir.txt" For Input As #ff
    While Not Eof(ff)
        Line Input #ff, linea
        content += linea & !"\n"
    Wend
    Close #ff
    
    Return content
End Function

' Function to apply changes
Sub ApplyChanges(oldFiles As String, newFiles As String)
    Dim As String oldLine, newLine, oldFilename, newFilename
    Dim As Integer oldFilenum, newFilenum, oldNextPos, newNextPos
    Dim As Integer oldPos = 1, newPos = 1
    
    ' Procesar cada línea
    While oldPos <= Len(oldFiles) And newPos <= Len(newFiles)
        ' Obtener la siguiente línea de oldFiles
        oldNextPos = Instr(oldPos, oldFiles, !"\n")
        If oldNextPos = 0 Then oldNextPos = Len(oldFiles) + 1
        oldLine = Mid(oldFiles, oldPos, oldNextPos - oldPos)
        oldPos = oldNextPos + 1
        
        ' Obtener la siguiente línea de newFiles
        newNextPos = Instr(newPos, newFiles, !"\n")
        If newNextPos = 0 Then newNextPos = Len(newFiles) + 1
        newLine = Mid(newFiles, newPos, newNextPos - newPos)
        newPos = newNextPos + 1
        
        If Len(Trim(oldLine)) > 0 And Len(Trim(newLine)) > 0 Then
            ' Extraer números y nombres de archivos
            oldFilenum = Valint(Left(oldLine, Instr(oldLine, " ") - 1))
            oldFilename = Mid(oldLine, Instr(oldLine, " ") + 1)
            newFilenum = Valint(Left(newLine, Instr(newLine, " ") - 1))
            newFilename = Mid(newLine, Instr(newLine, " ") + 1)
            
            ' Comprobar si el archivo fue renombrado
            If oldFilename <> newFilename Then
                Name oldFilename As newFilename
                Print "Renamed: " & oldFilename & " -> " & newFilename
            End If
        Elseif Len(Trim(oldLine)) > 0 And Len(Trim(newLine)) = 0 Then
            ' El archivo fue eliminado
            oldFilenum = Valint(Left(oldLine, Instr(oldLine, " ") - 1))
            oldFilename = Mid(oldLine, Instr(oldLine, " ") + 1)
            Kill oldFilename
            Print "Deleted: " & oldFilename
        End If
    Wend
End Sub

' Main program
Dim As String files, editedFiles
Dim As String editor
#ifdef __FB_LINUX__
    editor = "gedit"
#else
    editor = "notepad"
#endif

' Get current files
files = GetFiles()

' Write to temporary file
WriteToFile(files)

' Open editor
Shell editor & " temp_vidir.txt"

' Read edited file
editedFiles = ReadEditedFile()

' Apply changes
ApplyChanges(files, editedFiles)

' Clean up
Kill "temp_vidir.txt"

Print "Changes applied successfully."

Sleep

Implements basic functionality

package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

func panicFunc(err error) {
	if err != nil {
		panic(err)
	}
}

func main() {
	dir, err := os.Getwd()

	panicFunc(err)

	entries, err := os.ReadDir(dir)

	panicFunc(err)

	tmp, err := os.CreateTemp("", "dir")

	panicFunc(err)

	origDict := make(map[int]string)

	for i, entry := range entries {
		value := filepath.Join(dir, entry.Name())
		origDict[i] = value
		fmt.Fprintf(tmp, "%05d\t%s\n", i, value)
	}

	tmp.Close()

	name := tmp.Name()

	cmd := exec.Command(os.Getenv("EDITOR"), name)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	panicFunc(cmd.Run())

	tmp2, err := os.Open(name)
	panicFunc(err)

	defer tmp2.Close()

	scanner := bufio.NewScanner(tmp2)

	newDict := make(map[int]string)

	for scanner.Scan() {
		line := scanner.Text()
		items := strings.SplitN(line, "\t", 2)

		if len(items) < 2 {
			panic("Error")
		}

		key, value := items[0], items[1]
		numericKey, err := strconv.Atoi(key)

		panicFunc(err)

		_, ok := newDict[numericKey]

		if ok {
			panic("Can't have two repeating keys")
		}

		_, ok = origDict[numericKey]

		if !ok {
			panic("Invalid key")
		}

		newDict[numericKey] = value
	}

	for key := range origDict {
		origItem := origDict[key]
		newItem, ok := newDict[key]

		if !ok {
			panicFunc(os.RemoveAll(origItem))
			continue
		}

		if newItem != origItem {
			panicFunc(os.Rename(origItem, newItem))
		}
	}
}
###sysinclude dir.uh
###define SEPARATOR 9
#g argc 3 < { "." }{ 2 argv } sto mypath
@mypath 'd !istrue { ."The given directory doesn't exist! Exited.\n" end }
6 '- pidstr sto pidstring
@mypath getdir { ."Cannot load the dir! Aborted.\n" end } sto mydir
mypath per // If the end is not a '/' char, then add to...
10 mem sto aktfilename
10 mem sto neworiginalname
tömb @mydir dirlist
filtered tömb SEPARATOR [ #s 0 [-] "R" != 0 [-] "D" != #g & { dropline }{ 2 dropfield 2 dropfield
}
getline
]
"/tmp/furordiredit_tempfile-" sto myfilename
#s
@pidstring sum myfilename
".txt" sum myfilename
filtered @myfilename listtofile // print the list into a temporary file
"EDITOR" env sto mycommand
" "          sum mycommand
@myfilename  sum mycommand
@mycommand shell // execute the command
editedlist @myfilename filetolist
filtered SEPARATOR externalloop: ![ // Loop for the original list
[-?] !{ dropline } // If empty lines occured...
#s
zero foundflag editedlist SEPARATOR ![ // Loop for the edited list
// searching for the originaltype and originalnumber:
0 [-] 0 [-]§externalloop != { dropline }
1 [-] 1 [-]§externalloop != { dropline }
2 [-] 2 [-]§externalloop != {
2 [-] sbr §aktfilenamecreate
neworiginalname @mypath = 2 [-]§externalloop sum neworiginalname
@neworiginalname @aktfilename rename
."Renamed  :  " @neworiginalname print ."  ==>  " @aktfilename printnl
}
one foundflag [>]
] // end for the editedlist-loop
@foundflag !{ // No file or dir. In this case it must be deleted:
0 [-]§externalloop "D" == { // directory
2 [-] sbr §aktfilenamecreate
@aktfilename rmdir
."Deleted  :  " @aktfilename printnl
dropline
}
0 [-]§externalloop "R" == { // regular file
2 [-] sbr §aktfilenamecreate
@aktfilename removefile
."Deleted  :  " @aktfilename printnl
dropline
}
}

] // End of the loop for the original list

@myfilename removefile
end
aktfilenamecreate: aktfilename @mypath = sum aktfilename rts

{ „mydir” }
{ „tömb” }
{ „mypath” }
{ „filtered” }
{ „pidstring” }
{ „myfilename” }
{ „mycommand” }
{ „editedlist” }
{ „foundflag” }
{ „aktfilename” }
{ „neworiginalname” }
"""
From perl code at https://github.com/trapd00r/vidir/blob/master/bin/vidir

# vidir - edit directory

# Synopsis: vidir [--verbose] [directory|file|-] ...

# Description:
# vidir allows editing of the contents of a directory in a text editor. If no
# directory is specified, the current directory is edited.
#
# When editing a directory, each item in the directory will appear on its own
# numbered line. These numbers are how vidir keeps track of what items are
# changed. Delete lines to remove files from the directory, or
# edit filenames to rename files. You can also switch pairs of numbers to
# swap filenames.
#
# Note that if "-" is specified as the directory to edit, it reads a list of
# filenames from stdin and displays those for editing. Alternatively, a list
# of files can be specified on the command line.
"""

using ArgParse

const DEBUG = false

""" process command line arguments """
function parse_args2()
    s = ArgParseSettings()
    add_arg_table!(s,
        ["--verbose", "-v"],
        Dict(:help => "Verbosely display the actions taken by the program",
             :action => :store_true),
        "items",
        Dict(
            :help => "Directory, file, or - for stdin",
            :nargs => '*',
            :default => ["."])
    )
    return parse_args(ARGS, s)
end

""" vidir function, from Unix routine. See also documentation above """
function vidir()
    args = parse_args2()
    verbose = args["verbose"]  || DEBUG
    items = args["items"]
    error_occurred = false

    # Collect directory items
    dir = String[]
    for item in items
        if item == "-"
            append!(dir, readlines(stdin))
            # Reopen stdin to terminal
            try
                freopen("/dev/tty", "r", stdin)
            catch e
                error("Cannot reopen stdin: $e")
            end
        elseif isdir(item)
            item = rstrip(item, '/') * "/"
            try
                append!(dir, [item * f for f in sort(readdir(item))])
            catch e
                error("Cannot read $item: $e")
            end
        else
            push!(dir, item)
        end
    end

    # Check for control characters
    if any(x -> occursin(r"[[:cntrl:]]", x), dir)
        error("Control characters in filenames are not supported")
    end

    # Create temporary file
    tmp = tempname()
    open(tmp, "w") do out
        item_dict = Dict{Int,String}()
        c = 0
        for item in dir
            if !occursin(r"^(.*\/)?\.$|^(.*\/)?\.\.$", item)
                c += 1
                item_dict[c] = item
                println(out, "$c\t$item")
            end
        end
        flush(out)

        # Choose editor
        editor = ["vi"]
        if isfile("/usr/bin/editor")
            editor = ["/usr/bin/editor"]
        end
        if haskey(ENV, "EDITOR")
            editor = split(ENV["EDITOR"])
        end
        if haskey(ENV, "VISUAL")
            editor = split(ENV["VISUAL"])
        end

        # Run editor
        ret = run(`$editor $tmp`, wait=true)
        if !success(ret)
            error("$editor exited nonzero, aborting")
        end

        # Process edited file
        open(tmp, "r") do input
            for line in eachline(input)
                if isempty(strip(line))
                    continue
                end
                m = match(r"^(\d+)\t{0,1}(.*)", line)
                if isnothing(m)
                    error("Unable to parse line \"$line\", aborting")
                end
                num = parse(Int, m[1])
                name = m[2]
                if !haskey(item_dict, num)
                    error("Unknown item number $num")
                elseif name != item_dict[num]
                    if !isempty(name)
                        src = item_dict[num]
                        
                        if !isfile(src) && !islink(src)
                            println(stderr, "vidir: $src does not exist")
                            delete!(item_dict, num)
                            continue
                        end

                        # Handle swaps
                        if isfile(name) || islink(name)
                            tmp_name = name * "~"
                            c = 0
                            while isfile(tmp_name) || islink(tmp_name)
                                c += 1
                                tmp_name = name * "~$c"
                            end
                            try
                                mv(name, tmp_name)
                                verbose && println("'$name' -> '$tmp_name'")
                                for (k, v) in item_dict
                                    if v == name
                                        item_dict[k] = tmp_name
                                    end
                                end
                            catch e
                                @warn "vidir: failed to rename $name to $tmp_name: $e"
                                error_occurred = true
                            end
                        end

                        try
                            mv(src, name)
                            if isdir(name)
                                for (k, v) in item_dict
                                    item_dict[k] = replace(v, Regex("^$src") => name)
                                end
                            end
                            verbose && println("'$src' => '$name'")
                        catch e
                            @warn "vidir: failed to rename $src to $name: $e"
                            error_occurred = true
                        end
                    end
                end
                delete!(item_dict, num)
            end
        end


        # Clean up temporary files
        isfile(tmp * "~") && rm(tmp * "~")

        # Remove remaining items
        for item in sort(collect(values(item_dict)), rev=true)
            try
                if isdir(item) && !islink(item)
                    rmdir(item)
                else
                    rm(item)
                end
                verbose && println("removed '$item'")
            catch e
                @warn "vidir: failed to remove $item: $e"
                error_occurred = true
            end
        end
    end
    return error_occurred
end

""" run main """
vidir()
###sysinclude standard.uh
###sysinclude args.uh
###sysinclude list.uh
###sysinclude str.uh
###sysinclude io.uh
###define SEPARATOR 9
#g argc 3 < { "." }{ 2 argv } sto mypath
@mypath 'd inv istrue { ."The given directory doesn't exist! Exited.\n" end }
6 '- pidstr sto pidstring
@mypath getdir { ."Cannot load the dir! Aborted.\n" end } sto mydir
mypath per // Ha nem per-jelre végződik, kiegészíti vele.
10 mem sto aktstr
10 mem sto aktfilename
10 mem sto neworiginalname
"dr" sto types
1 mem !maximize sto szep
@szep 0 SEPARATOR inv []
@mydir ~r @mydir ~d + sto elemszám
//@elemszám 300 [[mem]] sto tömb
@elemszám 1 [[mem]] sto tömb
zero index
types {~ @mydir {~?~} <-~ {{ #g
1 sto() aktstr~
@aktstr 0 {~?~} #k uppercase inv []
#s @szep sum aktstr
{{}} #g !(#s) !trim dup sum aktstr inv mem
@szep sum aktstr
@mydir {~?~} {{}} getfilename sum aktstr
@aktstr @index [[tömb]]=
#g ++() index
}}
~}
"/tmp/peridiredit_tempfile-" sto myfilename
#s
@pidstring sum myfilename
".txt" sum myfilename
@tömb @myfilename listtofile! // print the list into a temporary file
"EDITOR" env sto mycommand
" "          sum mycommand
@myfilename  sum mycommand

@mycommand shell // execute the command

@myfilename filetolist sto editedlist
//@editedlist [[print]]! end

tömb~ externalloop: {{ // Loop for the original list
#g {{}} @[[tömb]] ~ inv { {{<}} } // If empty lines occured...
zero foundflag
editedlist~ {{ // loop for the edited list
// searching for the originaltype and originalnumber:
#g
{{}} [[editedlist]][0] {{}}§externalloop [[tömb]][0] != { {{<}} }
SEPARATOR {{}}              1 [[editedlist]][_] sto uj
SEPARATOR {{}}§externalloop 1 [[tömb]][_]       sto er
#s @er @uj != { @er inv mem @uj inv mem {{<}} }

SEPARATOR {{}}              2 [[editedlist]][_] -- sto uj // trim the NL char at the end of the string
SEPARATOR {{}}§externalloop 2 [[tömb]][_]          sto er
#s @er @uj != {
aktfilename     @mypath = @er sum aktfilename
neworiginalname @mypath = @uj sum neworiginalname
@aktfilename @neworiginalname rename
."Renamed  :  " @aktfilename printnl
."       ==>  " @neworiginalname printnl
}
@er inv mem @uj inv mem
one foundflag
}} // editedlist~
@foundflag inv { // Nincs meg a file vagy dir. Ekkor le kell törölni:
{{}} [[tömb]][0] 'D #g == { // directory
SEPARATOR {{}} 2 [[tömb]][_] sto er
aktfilename @mypath #s = @er sum aktfilename @er inv mem
@aktfilename rmdir
."Deleted  :  " @aktfilename sprintnl
{{<}}
}
{{}} [[tömb]][0] 'R #g == { // Regular file
SEPARATOR {{}} 2 [[tömb]][_] sto er
aktfilename @mypath #s = @er sum aktfilename @er inv mem
@aktfilename removefile
."Deleted  :  " @aktfilename sprintnl
{{<}}
}
}
}} // tömb~
@myfilename removefile
end

{ „er” }
{ „uj” }
{ „szep” }
{ „types” }
{ „index” }
{ „mydir” }
{ „tömb” }
{ „mypath” }
{ „aktstr” }
{ „elemszám” }
{ „pidstring” }
{ „myfilename” }
{ „mycommand” }
{ „editedlist” }
{ „foundflag” }
{ „aktfilename” }
{ „neworiginalname” }

Should work on Windows and Linux. I have commented out all the actually destructive statements.

-- demo/rosetta/vidir.exw
string directory = ".",
       editor = iff(platform()=WINDOWS?"notepad":"gedit"),
       workfile = "vidir.txt"
--     filter = ""
--bool recursive = false,
--     overwrite = false,
--     verbose = false      -- ,... etc

procedure process_command_line()
    sequence cl = command_line()[3..$]
    if length(cl) then
        -- I assume you can figure out how to deal with eg
        -- {`-d`,`C:\Users\Pete\Documents`,`-e`,`notepad++`}
        ?{"command line arguments (ignored)",cl}
    end if
end procedure

process_command_line()
sequence d = dir(directory)
if d=-1 then
    crash("no such directory")
end if
d = d[3..$] -- (drop "." and "..")
integer fn = open(workfile,"w")
for i=1 to length(d) do
    printf(fn,"%d: %s\n",{i,d[i][D_NAME]})
end for
close(fn)
{} = system_exec(editor&" "&workfile)
object lines = get_text(workfile,GT_LF_STRIPPED)
integer last = 0
if lines=-1 then
    crash("error reading edited file")
end if
for i=1 to length(lines) do
    sequence r = scanf(lines[i],"%d: %s")
    if r={} then
        crash("error parsing line")
    end if
    {{integer line, string name}} = r
    for last=last+1 to line-1 do
        printf(1,"delete_file(%s)\n",{d[last][D_NAME]})
        if not file_exists(d[last][D_NAME]) then ?9/0 end if
--      if not delete_file(d[last][D_NAME]) then
--          crash("error deleting file")
--      end if
    end for
    string prev = d[line][D_NAME]
    if prev!=name then
        printf(1,"rename_file(%s,%s)\n",{prev,name})
        if not file_exists(prev) then ?9/0 end if
        if file_exists(name) then ?9/0 end if
--      if name[2]=':' and name[1]!=prev[1] then
--          if not move_file(prev,name) then
--              crash(error moving file")
--          end if
--      elsif not rename_file(prev,name) then
--          crash("error renaming file")
--      end if
    end if
    last = line
end for
?"done"
{} = wait_key()
Works with: Rakudo version 2020.05

This is a fairly faithful recreation of the vidir utility with some extra "enhancements". It is set up to work seamlessly with Linux, OSX and most unix-like operating systems. Probably could be adapted for Windows too but won't work there as is.

Takes several positional and/or named command line parameters:


Positionals:

  • path, string, optional, defaults to the present directory (relative './').
  • filter, string, optional, defaults to none. A regex assertion you can use to filter the returned file names. E.G. \.txt$ (filenames ending with .txt extension)


Named:

  • -r or --recurse, flag, optional, default False. Recurse into nested directories and process those files as well.
  • -v or --verbose, flag, optional, default False. Be chatty about what is going on when making changes.
  • --e=whatever or --editor=whatever, string, optional, defaults the default text editor. Pass in a command name to specify a specific editor: (E.G. --editor=vim)


Will get a list of all file names from the specified director(y|ies), put them in a temporary file and open the text file with the default (or specified) text editor.

Edit the file names or directory names in the text editor: add, remove, modify file names, then save the text file; all of the modifications made to the text file will be applied to the file system.

To edit a file name: Just edit the file name, extension, whatever. Warning! There is nothing preventing you from using characters in the file name which will make it difficult to open, modify or delete the file. You can screw yourself quite easily if you make an effort. With great power comes great responsibility. The foot cannon is primed and loaded.

If you add a forward solidus (directory separator: /) to a filename, it will treat anything between separators as a directory name and WILL CREATE non-existent directories.

To move a file: Edit the directory path. The file will be moved to the new path. Non-existing directories will be created.

To delete a file: Delete the entire line (file name and identifier) in the text file.

To add a file: Add a new line with a unique integer (need not be in any order), one or more tabs, then the new file path/name. May be a relative or absolute path.

Notice that all of the above operations will fail to apply if you lack sufficient permissions for the affected files or directories.

use Sort::Naturally;
use File::Temp;

my %*SUB-MAIN-OPTS = :named-anywhere;

unit sub MAIN (
    Str $path         = '.',    #= default $path
    Str $filter       = '',     #= default file filter
    Bool :r(:$recurse)= False,  #= recursion flag
    Bool :v(:$verbose)= False,  #= verbose mode
    Str :e(:$editor)  = $*DISTRO ~~ /'Darwin'/ ?? "open" !! "xdg-open"; #= default editor
);

my $dir = $path;

# fix up path if necessary
$dir ~= '/' unless $dir.substr(*-1) eq '/';

# check that path is reachable
die "Can not find directory $dir" unless $dir.IO.d;


my @files;

# get files from that path
getdir( $dir, $filter );

@files.= sort( &naturally );

# set up a temp file and file handle
my ($filename, $filehandle) = tempfile :suffix('.vidir');

# write the filenames to the tempfile
@files.kv.map: { $filehandle.printf("%s\t%s\n", $^k, $^v) };

# flush the buffer to make sure all of the filenames have been written
$filehandle.flush;

# editor command
my $command = "$editor $filename";

# start text editor; suppress STDERR, some editors complain about open files being deleted
shell("$command 2> /dev/null");

react {
    # watch for file changes
    whenever IO::Notification.watch-path($filename) {
        # allow a short interval for the file to finish writing
        sleep .1;

        # read in changed file
        my %changes = $filename.IO.lines.map( { my ($k, $v) = .split(/\t+/); "{$k.trim}" => $v} );

        # walk the filenames and make the desired changes
        for ^@files -> $k {
            if %changes{"$k"}:exists {
                # name has changed, rename the file on disk
                if (%changes{"$k"}) ne @files[$k] {
                    # check to see that the desired directory exists
                    checkdir %changes{"$k"};
                    # notify and do it
                    say "Renaming: {@files[$k]} to " ~ %changes{"$k"} if $verbose;
                    rename @files[$k], %changes{"$k"} orelse .die;
                }
                %changes{"$k"}:delete;
            }
            else {
                # name is gone, delete the file
                # notify and do it
                say "Deleting: {@files[$k]}" if $verbose;
                @files[$k].unlink orelse .die;
            }
        }
        for %changes.kv -> $k, $v {
            # a new name is added, add an empty file with that name
            # check to see that the desired directory exists
            checkdir $v;
            # notify and do it
            say "Adding: $v" if $verbose;
            shell("touch $v") orelse .die;
        }
        # clean up when done
        done;
        exit;
    };
    # watch for CTRL-C, cleanup and exit
    whenever signal(SIGINT) {
        print "\b\b";
        done;
        exit;
    }
}

# get the files in a specified directory matching the filter parameter
sub files ( $dir, $filter = '' ) {
    if $filter.chars {
        $dir.IO.dir.grep( *.f ).grep( *.basename.contains(/<$filter>/) );
    } else {
        $dir.IO.dir.grep( *.f );
    }
}

# get the files in the present directory and recurse if desired
sub getdir ($dir, $filter) {
    if $recurse {
        @files.append: files($dir, $filter);
        getdir( $_, $filter ) for $dir.IO.dir.grep( *.d );
    } else {
        @files = files($dir, $filter);
    }
}

# check for existence of a directory and create it if not found
sub checkdir ($dir) {
    unless $dir.IO.dirname.IO.e {
        # if not, create it
        my @path = $dir.IO.dirname.split('/');
        for 1 .. @path {
            my $thispath = @path[^$_].join('/');
            unless $thispath.IO.e {
                say "Creating new directory $thispath" if $verbose;
                mkdir($thispath);
            }
        }
    }
}

This program, written in User RPL, can modify the name of one or more objects - variable or program, including itself - in the current directory, but cannot delete any of them: RPL "full-line" editor does not allow to identify which name(s) are removed from the list.

Works with: HP version 48G
"VIDIR" 
   { { "OBJ: " "?" 5 } }
   { }
   VARS 1 →LIST DUP
   INFORM VARS
   → newnames oldnames
   ≪ IF newnames SIZE oldnames SIZE == THEN
         1 oldnames SIZE FOR j
            oldnames j GET
            DUP RCL 
            "'~tmp" j + STR→ STO
            PURGE
         NEXT
         1 oldnames SIZE FOR j
            "'~tmp" j + STR→ 
            DUP RCL 
            newnames j GET STO
            PURGE
         NEXT
      END
≫ ≫ 'VIDIR' STO
Library: Wren-fmt
Library: Wren-ioutil

Just a basic solution.

Although any directory can be used, only files (not sub-directories) can be processed and filtering is not supported.

As Wren-cli cannot currently run external processes such as text editors, we instead present the user with each file in turn and ask them to confirm whether it is to be left unchanged, the name is to be changed or the file is to be deleted. The maximum number of files to be processed has been limited to 999 because of this.

The amended file is then saved to disk and the changes processed, echoing name changes/deletions to the terminal if verbose is 'on'.

This has been written to work on Linux but should work on other platforms with minor changes.

import "os" for Process
import "io" for Directory, File
import "./fmt" for Fmt
import "./ioutil" for FileUtil, Input

var args = Process.arguments
if (args.count > 2) {
    System.print("Too many arguments - maximum is two.")
}
var verbose = false
var path = ""
if (args.count == 0) {
    path = "./"  // current directory
} else if (args.count == 1) {
    if (args[0] == "--v" || args[0] == "--verbose") {
        verbose = true
        path = "./"
    } else {
        path = args[0]
    }
} else if (args[0] == "--v" || args[0] == "--verbose") {
    verbose = true
    path = args[1]
} else {
    System.print("First argument is invalid, must be --v(erbose)")
    return
}
if (!Directory.exists(path)) {
    System.print("Unable to find directory : %(path)")
    return
}

// ignore sub-directories and other special files
if (!path.endsWith("/")) path = path + "/"
var fileNames = Directory.list(path).where { |f| File.exists(path + f) }.toList
if (fileNames.count == 0) {
    System.print("There are no files in directory: %(path)")
    return
} else if (fileNames.count > 999) {
    System.print("There are too many files to process - maximum is 999.")
    return
}
var origNames = []  // keep a list of the original file names and their index numbers
var ix = 1
File.create("vidir.txt") { |f|
    for (fileName in fileNames) {
        var ixs = Fmt.dz(3, ix) // 3 digits, zero filled
        f.writeBytes(Fmt.swrite("$s $s\n", ixs, fileName))
        origNames.add([ixs, fileName])
        ix = ix + 1
    }
}

// create a new file with amended details
File.create("vidir2.txt") { |f2|
    var lines = FileUtil.readLines("vidir.txt")
    for (line in lines) {
        if (line == "") continue  // get rid of any extraneous blank lines
        System.print(line)
        var action = Input.option("  (p)ass, (a)mend, (d)elete ? : ", "padPAD")
        if (action == "p" || action == "P") {
            f2.writeBytes(line + "\n")
            continue
        }
        if (action == "d" || action == "D") {
            continue
        }
        var name = Input.text("  Enter amended file name : ", 1)
        f2.writeBytes(line[0..3] + name + "\n")
    }
}

// change vidir2.txt to vidir.txt overwriting original file
FileUtil.move("vidir2.txt", "vidir.txt", true)

// process by first creating a map of the new names by index number
var newNames = {}
var lines = FileUtil.readLines("vidir.txt")
for (line in lines) {
    if (line == "") continue
    var split = line.split(" ")
    newNames[split[0]] = split[1]
}

// now iterate through the origNames list and pass/amend/delete as appropriate
System.print()
if (verbose) System.print("The following changes are being made:")
for (origName in origNames) {
    var ixs = origName[0]
    var old = origName[1]
    var new = newNames[ixs]
    if (new == null) {  // file to be deleted }
        File.delete(path + old)
        if (verbose) System.print("  Deleting '%(old)'")
    } else if (new != old) { // file name to be changed
        FileUtil.move(path + old, path + new, true)
        if (verbose) System.print("  Changing '%(old)' to '%(new)'")
    }
}
if (verbose) System.print("All changes have now been processed.")
Output:

For testing purposes I've created a sub-directory called 'test' off the current directory and placed four files in it: a.txt, b.txt, c.txt and d.txt

$ wren vidir.wren --v test
001 a.txt
  (p)ass, (a)mend, (d)elete ? : p
002 b.txt
  (p)ass, (a)mend, (d)elete ? : a
  Enter amended file name : bb.txt
003 c.txt
  (p)ass, (a)mend, (d)elete ? : d
004 d.txt
  (p)ass, (a)mend, (d)elete ? : a
  Enter amended file name : dd.txt

The following changes are being made:
  Changing 'b.txt' to 'bb.txt'
  Deleting 'c.txt'
  Changing 'd.txt' to 'dd.txt'
All changes have now been processed.

$ cat vidir.txt
001 a.txt
002 bb.txt
004 dd.txt

$ dir test
a.txt  bb.txt  dd.txt