phroessler was looking for a safe rm replacement and found this: trash.tcl
I made some improvements to it:
#!/usr/bin/tclsh
## From https://gist.githubusercontent.com/adamnew123456/1321420/raw/560ae2253e6daa7d08a97aa16ca853d968f628a7/trash.tcl
# Copyright 2026 Philipp Roessler
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
set filepath "$env(HOME)/.local/share/Trash/files"
set infopath "$env(HOME)/.local/share/Trash/info"
proc basename {path} {
return [file tail $path]
}
proc trash {path} {
set bp [basename $path]
set fp [open "$::infopath/$bp.trashinfo" "w"]
puts $fp "\[Trash Info\]"
puts $fp "Path=[pwd]/[basename $path]"
puts $fp "DeletionDate=[clock format [clock seconds] -format "%Y-%m-%dT%T"]"
close $fp
file rename $path "$::filepath/[basename $path]"
}
proc untrash {fname} {
set fp [open "$::infopath/$fname.trashinfo"]
set txt [read $fp]
foreach line [split $txt "\n"] {
set re [string compare [string range $line 0 4] "Path="]
if {$re == 0} {
set path [lindex [split $line "="] 1]
break
}
}
close $fp
file delete "$::infopath/$fname.trashinfo"
file rename "$::filepath/$fname" $path
}
proc returnAllFiles {aPath} {
# Return sorted list of hidden and unhidden files and folders except of "." and ".."
set lFileList [lsort [lsearch -all -inline -not -regexp [list {*}[glob -nocomplain -directory "$aPath" -tails -types hidden -- "*"] {*}[glob -nocomplain -directory "$aPath" -tails -- "*"]] {^[.]{1,2}$}]]
return $lFileList
}
proc view {} {
foreach lFile [returnAllFiles "$::filepath"] {
puts [basename $lFile]
}
}
proc clean {fname} {
file delete "$::infopath/$fname.trashinfo"
file delete -force "$::filepath/$fname"
}
proc help {} {
puts "Trash - A simple program to manage your trash bin"
puts "Usage trash \[-t|-u|-e|-l|-c|-n|-h\] \[files...\]"
puts " -t - Trash all listed files"
puts " -u - Untrash all listed files"
puts " -e - Untrash (evacuate) all files"
puts " -l - List all files"
puts " -c - Permanently delete (clean) all listed files"
puts " -n - Permanently delete all files (nuke)"
puts " -h - Print this message"
}
if {$argc < 1} {
help
exit
}
set option [lindex $argv 0]
set filename_suffix [join "[clock milli] [string range [expr rand ()] 2 end]" "_"]
if {[string compare $option "-t"] == 0} {
foreach item [lrange $argv 1 end] {
set new_name [join [list [string trimright "$item" "/"] "$filename_suffix"] "_"]
file rename $item $new_name
trash $new_name
}
} elseif {[string compare $option "-u"] == 0} {
foreach item [lrange $argv 1 end] {
untrash $item
}
} elseif {[string compare $option "-e"] == 0} {
foreach item [returnAllFiles "$::filepath"] {
untrash [file tail $item]
}
} elseif {[string compare $option "-l"] == 0} {
view
} elseif {[string compare $option "-c"] == 0} {
foreach item [lrange $argv 1 end] {
clean $item
}
} elseif {[string compare $option "-n"] == 0} {
foreach item [returnAllFiles "$::filepath"] {
clean [file tail $item]
}
} else {
help
}