Logo ROOT  
Reference Guide
Loading...
Searching...
No Matches
rootrm.cxx
Go to the documentation of this file.
1/// \file rootrm.cxx
2///
3/// Command line tool to remove objects from ROOT files
4///
5/// \author Giacomo Parolini <giacomo.parolini@cern.ch>
6/// \date 2026-02-18
7#include <ROOT/RLogger.hxx>
8
9#include "logging.hxx"
10#include "optparse.hxx"
11#include "RootObjTree.hxx"
12#include "RootObjTree.cxx"
13
14#include <TClass.h>
15#include <TError.h>
16#include <TFile.h>
17#include <TROOT.h>
18#include <TSystem.h>
19
20#include <algorithm>
21#include <iostream>
22#include <memory>
23#include <string_view>
24#include <vector>
25
26using namespace ROOT::CmdLine;
27
28static const char *const kShortHelp = "usage: rootrm [-h] [-i|--interactive] [-r|--recursive] "
29 "[-v|--verbose] FILE[:path/to/obj] [...]\n";
30static const char *const kLongHelp = R"(
31Remove objects from ROOT files
32
33positional arguments:
34 FILE:path File(s) and path of objects to remove
35
36options:
37 -f, --force ignore nonexistent files and arguments, never prompt
38 -h, --help show this help message and exit
39 -i, --interactive prompt before deleting each object
40 -n, --dry-run show what would be removed, but don't actually remove anything. Implies `-v`.
41 -r, --recursive recurse inside directories
42 -v, --verbose be verbose (can be repeated for increased verbosity)
43
44examples:
45- rootrm example.root:hist
46 Remove the object 'hist' from the ROOT file 'example.root'
47
48- rootrm example.root:dir/hist
49 Remove the object 'hist' from the directory 'dir' inside the ROOT file 'example.root'
50
51- rootrm example.root
52 Remove the ROOT file 'example.root'
53
54- rootrm -i example.root:hist
55 Display a confirmation request before deleting: 'remove 'hist' from 'example.root' ? (y/n) :'
56)";
57
58struct RootRmArgs {
59 enum class EPrintUsage {
63 };
65 bool fInteractive = false;
66 bool fRecursive = false;
67 bool fDryRun = false;
68 bool fForce = false;
69 std::vector<std::string> fSources;
70};
71
72static RootRmArgs ParseArgs(const char **args, int nArgs)
73{
75
76 RootRmArgs outArgs;
77
78 RCmdLineOpts opts;
79 opts.AddFlag({"-f", "--force"});
80 opts.AddFlag({"-h", "--help"});
81 opts.AddFlag({"-i", "--interactive"});
82 opts.AddFlag({"-n", "--dry-run"});
83 opts.AddFlag({"-r", "--recursive"});
85
86 opts.Parse(args, nArgs);
87
88 for (const auto &err : opts.GetErrors()) {
89 std::cerr << err << "\n";
90 }
91 if (!opts.GetErrors().empty()) {
93 return outArgs;
94 }
95
96 if (opts.GetSwitch("help")) {
98 return outArgs;
99 }
100
101 outArgs.fForce = opts.GetSwitch("force");
102 outArgs.fInteractive = opts.GetSwitch("interactive");
103 outArgs.fRecursive = opts.GetSwitch("recursive");
104 outArgs.fDryRun = opts.GetSwitch("dry-run");
105
106 // fDryRun implies (at least) verbosity = 2.
107 int verbosity = std::max(opts.GetSwitch("v") + 1, outArgs.fDryRun + 1);
108 SetLogVerbosity(verbosity);
109
110 outArgs.fSources = opts.GetArgs();
111 if (outArgs.fSources.size() < 1)
113
114 if (outArgs.fForce && outArgs.fInteractive) {
115 Err() << "-i and -f flags are mutually exclusive.\n";
117 }
118
119 return outArgs;
120}
121
122static bool PromptForRemoval(std::string_view fileName, std::string_view objName)
123{
124 if (objName.empty())
125 std::cout << "remove '" << fileName << "' ? (y/n) ";
126 else
127 std::cout << "remove '" << objName << "' from '" << fileName << "' ? (y/n) ";
128 std::string answer;
129 std::cin >> answer;
130 return answer == 'y' || answer == 'Y';
131}
132
133// Returns true if the entire file was deleted.
134static bool RemoveNode(RootSource &src, NodeIdx_t nodeIdx, const RootRmArgs &args)
135{
136 // nodeIdx must be in range because it always comes from a RootObjTree.
137 assert(nodeIdx < src.fObjectTree.fNodes.size());
138 const RootObjNode &node = src.fObjectTree.fNodes[nodeIdx];
139 const bool nodeIsRootFile = !node.fKey;
140 std::string_view objName = nodeIsRootFile ? std::string_view{} : node.fName;
141
142 if (node.fDir && !args.fRecursive) {
143 if (nodeIsRootFile)
144 Err() << "cannot remove '" << node.fName << "': is a ROOT file. Use -r to remove it.\n";
145 else
146 Err() << "cannot remove '" << node.fName << "': is a directory. Use -r to remove it.\n";
147 return false;
148 }
149
150 const bool doRemove = !args.fInteractive || PromptForRemoval(src.fFileName, objName);
151 if (!doRemove)
152 return false;
153
154 if (!args.fDryRun) {
155 if (nodeIsRootFile) {
156 // delete the entire file.
157 src.fObjectTree.fFile.reset();
158 const auto res = gSystem->Unlink(src.fFileName.c_str());
159 if (res < 0 && !args.fForce) {
160 Err() << "failed to remove '" << src.fFileName << "': " << gSystem->GetErrorStr() << "\n";
161 }
162 return res == 0;
163 } else {
164 if (node.fDir) {
165 // Reset the kIsDirectoryFile bit to allow deleting the directory key (normally not allowed).
166 // Two considerations:
167 // 1. we need to use BIT(14) because TKey::kIsDirectoryFile is private;
168 // 2. this is safe to do because we're not gonna read the directory anymore until the end of the program,
169 // so we can circumvent the safety measure imposed by TKey for directories.
170 node.fKey->ResetBit(BIT(14));
171 }
172 Option_t *opt = GetLogVerbosity() > 2 ? "v" : "";
173 node.fKey->Delete(opt);
174 }
175 }
176
177 Info(2) << "removed '" << NodeFullPath(src.fObjectTree, nodeIdx, ENodeFullPathOpt::kIncludeFilename) << "'\n";
178 return false;
179}
180
181int main(int argc, char **argv)
182{
183 InitLog("rootrm");
184
185 // Parse arguments
186 auto args = ParseArgs(const_cast<const char **>(argv) + 1, argc - 1);
187 if (args.fPrintHelp != RootRmArgs::EPrintUsage::kNo) {
188 std::cerr << kShortHelp;
189 if (args.fPrintHelp == RootRmArgs::EPrintUsage::kLong) {
190 std::cerr << kLongHelp;
191 return 0;
192 }
193 return 1;
194 }
195
196 // Validate and split all input sources into filename + pattern
197 std::vector<std::pair<std::string_view, std::string_view>> sourcesFileAndPattern;
198 sourcesFileAndPattern.reserve(args.fSources.size());
199 for (const auto &src : args.fSources) {
200 auto res = SplitIntoFileNameAndPattern(src);
201 if (!res) {
202 Err() << res.GetError()->GetReport() << "\n";
203 return 1;
204 }
205 auto fNameAndPattern = res.Unwrap();
206 sourcesFileAndPattern.push_back(fNameAndPattern);
207 }
208
209 const std::uint32_t flags = kOpenFilesAsWritable | (args.fRecursive * EGetMatchingPathsFlags::kRecursive);
210 std::vector<std::string_view> filesDeleted;
211 bool errors = false;
212 for (const auto &[srcFname, srcPattern] : sourcesFileAndPattern) {
213 // If we already deleted this file, drop this source referring to it.
214 if (std::find(filesDeleted.begin(), filesDeleted.end(), srcFname) != filesDeleted.end()) {
215 continue;
216 }
217
218 auto src = ROOT::CmdLine::GetMatchingPathsInFile(srcFname, srcPattern, flags);
219 if (!src.fErrors.empty()) {
220 // ignore errors with -f flag
221 if (args.fForce)
222 continue;
223
224 for (const auto &err : src.fErrors)
225 Err() << err << "\n";
226
227 errors = true;
228 break;
229 }
230
231 // We should never register files to the global list for performance reasons.
232 assert(!gROOT->GetListOfFiles()->Contains(src.fObjectTree.fFile.get()));
233
234 // Iterate all objects we need to remove
235 for (auto nodeIdx : src.fObjectTree.fLeafList) {
236 if (RemoveNode(src, nodeIdx, args)) {
237 filesDeleted.push_back(srcFname);
238 break;
239 }
240 }
241 for (auto nodeIdx : src.fObjectTree.fDirList) {
242 if (RemoveNode(src, nodeIdx, args)) {
243 filesDeleted.push_back(srcFname);
244 break;
245 }
246 }
247 }
248
249 return errors;
250}
const char Option_t
Option string (const char).
Definition RtypesCore.h:80
#define BIT(n)
Definition Rtypes.h:91
void Info(const char *location, const char *msgfmt,...)
Use this function for informational messages.
Definition TError.cxx:241
Double_t err
#define gROOT
Definition TROOT.h:417
externTSystem * gSystem
Definition TSystem.h:582
std::vector< double > errors
void AddFlag(std::initializer_list< std::string_view > aliases, EFlagType type=EFlagType::kSwitch, std::string_view help="", std::uint32_t flagOpts=0)
Defines a new flag (either a switch or a flag with argument).
Definition optparse.hxx:208
const std::vector< std::string > & GetErrors() const
Returns all parsing errors.
Definition optparse.hxx:180
int GetSwitch(std::string_view name) const
If name refers to a previously-defined switch (i.e.
Definition optparse.hxx:300
const std::vector< std::string > & GetArgs() const
Retrieves all positional arguments.
Definition optparse.hxx:182
void Parse(const char **args, std::size_t nArgs)
Definition optparse.hxx:425
@ kFlagAllowMultiple
Flag is allowed to appear multiple times (default: it's an error to see the same flag twice).
Definition optparse.hxx:131
int main()
subroutine node(ivo, nuserm, iposp)
Definition g2root.f:833
void SetLogVerbosity(int verbosity)
Definition logging.hxx:45
int GetLogVerbosity()
Definition logging.hxx:50
void InitLog(const char *name, int defaultVerbosity=1)
Definition logging.hxx:39
std::ostream & Err()
Definition logging.hxx:55
ROOT::RResult< std::pair< std::string_view, std::string_view > > SplitIntoFileNameAndPattern(std::string_view sourceRaw)
Given a string like "root://file.root:a/b/c", splits it into { "root://file.root",...
std::string NodeFullPath(const RootObjTree &tree, NodeIdx_t nodeIdx, ENodeFullPathOpt opt)
Given a node, returns its full path. If opt == kIncludeFilename, the path is prepended by "filename....
@ kRecursive
Recurse into subdirectories when matching objects.
std::uint32_t NodeIdx_t
RootSource GetMatchingPathsInFile(std::string_view fileName, std::string_view pattern, std::uint32_t flags)
Given a file and a "path pattern", returns a RootSource containing the tree of matched objects.
static const char *const kShortHelp
Command line tool to open a ROOT file on a TBrowser.
static const char *const kLongHelp
static bool RemoveNode(RootSource &src, NodeIdx_t nodeIdx, const RootRmArgs &args)
Definition rootrm.cxx:134
static bool PromptForRemoval(std::string_view fileName, std::string_view objName)
Definition rootrm.cxx:122
static RootRmArgs ParseArgs(const char **args, int nArgs)
Definition rootrm.cxx:72
std::unique_ptr< TFile > fFile
std::vector< RootObjNode > fNodes
bool fInteractive
Definition rootrm.cxx:65
bool fRecursive
Definition rootrm.cxx:66
std::vector< std::string > fSources
Definition rootrm.cxx:69
EPrintUsage fPrintHelp
Definition rootrm.cxx:64
bool fDryRun
Definition rootrm.cxx:67
bool fForce
Definition rootrm.cxx:68