shell-cmd v1.2
Recursively find files matching a regex and execute a shell command for each match.
Loading...
Searching...
No Matches
cmd.cpp File Reference

shell-cmd v1.2 — Recursively find files matching a regex and execute a shell command for each match. More...

#include "argz.hpp"
#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <format>
#include <grp.h>
#include <iostream>
#include <pwd.h>
#include <regex>
#include <signal.h>
#include <span>
#include <string>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
Include dependency graph for cmd.cpp:

Go to the source code of this file.

Classes

struct  SizeFilter
 Holds a parsed size filter with comparison operator and byte threshold. More...
struct  TimeFilter
 Holds a parsed modification-time filter with comparison operator and day count. More...
struct  Options
 Aggregates all runtime options parsed from the command line. More...
struct  Stats
 Tracks execution statistics printed in the summary. More...

Enumerations

enum class  CmpOp { EQ , LT , GT }
 Comparison operator for size and time filters. More...

Functions

SizeFilter parse_size_filter (const std::string &s)
 Parse a size filter string into a SizeFilter.
TimeFilter parse_time_filter (const std::string &s)
 Parse a time filter string into a TimeFilter.
bool matches_filters (const fs::directory_entry &entry)
 Test a directory entry against all active metadata filters.
bool proc_cmd (const std::string &cmd, std::span< const std::string > text)
 Substitute placeholders in a command template and execute the result.
void wait_for_slot ()
 Block until a parallel execution slot is available.
void wait_all ()
 Wait for all outstanding child processes to finish.
std::string replace_string (std::string orig, const std::string &with, const std::string &rep)
 Replace all occurrences of a substring within a string.
void add_directory (const fs::path &path, const std::string &cmd, const std::string &regex_str, std::vector< std::string > &args, int depth)
 Recursively walk a directory, match entries against a regex and filters, and run commands.
int System (const std::string &command)
 Execute a shell command using fork/exec with proper signal handling.
void print_help (const char *prog)
 Print usage information to stdout.
int main (int argc, char **argv)
 Program entry point.

Variables

static Options opts
 Global runtime options.
static Stats stats
 Global execution statistics.
static std::vector< pid_t > child_pids
 PIDs of outstanding child processes (parallel mode).
static bool stop_requested = false
 Set to true when stop-on-error triggers.

Detailed Description

shell-cmd v1.2 — Recursively find files matching a regex and execute a shell command for each match.

Walks a directory tree using std::filesystem, applies metadata filters (size, time, permissions, ownership, type), substitutes placeholders in a command template, and executes the resulting command for every matched entry. Supports parallel execution, exclude patterns, confirm mode, and stop-on-error.

See also
https://lostsidedead.biz @license GNU GPL v3

Definition in file cmd.cpp.

Enumeration Type Documentation

◆ CmpOp

enum class CmpOp
strong

Comparison operator for size and time filters.

Enumerator
EQ 
LT 
GT 

Definition at line 32 of file cmd.cpp.

32{ EQ, LT, GT };
@ EQ
Definition cmd.cpp:32
@ LT
Definition cmd.cpp:32
@ GT
Definition cmd.cpp:32

Function Documentation

◆ parse_size_filter()

SizeFilter parse_size_filter ( const std::string & s)

Parse a size filter string into a SizeFilter.

Parameters
sFilter string, e.g. "+10M", "-1K", "4096". Prefix '+' means greater-than, '-' means less-than, no prefix means exact. Suffix K/M/G multiplies by 1024/1048576/1073741824.
Returns
A populated SizeFilter with active=true.

Definition at line 96 of file cmd.cpp.

96 {
97 SizeFilter f;
98 f.active = true;
99 std::string val = s;
100 if (val[0] == '+') {
101 f.op = CmpOp::GT;
102 val = val.substr(1);
103 } else if (val[0] == '-') {
104 f.op = CmpOp::LT;
105 val = val.substr(1);
106 } else {
107 f.op = CmpOp::EQ;
108 }
109 uintmax_t multiplier = 1;
110 char suffix = val.back();
111 if (suffix == 'K' || suffix == 'k') {
112 multiplier = 1024;
113 val.pop_back();
114 } else if (suffix == 'M' || suffix == 'm') {
115 multiplier = 1024 * 1024;
116 val.pop_back();
117 } else if (suffix == 'G' || suffix == 'g') {
118 multiplier = 1024ULL * 1024 * 1024;
119 val.pop_back();
120 }
121 f.bytes = std::stoull(val) * multiplier;
122 return f;
123}
Holds a parsed size filter with comparison operator and byte threshold.
Definition cmd.cpp:35
CmpOp op
Comparison direction (equal, less-than, greater-than).
Definition cmd.cpp:37
bool active
Whether this filter is enabled.
Definition cmd.cpp:36
uintmax_t bytes
Size threshold in bytes.
Definition cmd.cpp:38

References SizeFilter::active, SizeFilter::bytes, EQ, GT, LT, and SizeFilter::op.

Referenced by main().

Here is the caller graph for this function:

◆ parse_time_filter()

TimeFilter parse_time_filter ( const std::string & s)

Parse a time filter string into a TimeFilter.

Parameters
sFilter string, e.g. "+7", "-1", "3". Prefix '+' means older-than, '-' means newer-than, no prefix means exact.
Returns
A populated TimeFilter with active=true.

Definition at line 131 of file cmd.cpp.

131 {
132 TimeFilter f;
133 f.active = true;
134 std::string val = s;
135 if (val[0] == '+') {
136 f.op = CmpOp::GT;
137 val = val.substr(1);
138 } else if (val[0] == '-') {
139 f.op = CmpOp::LT;
140 val = val.substr(1);
141 } else {
142 f.op = CmpOp::EQ;
143 }
144 f.days = std::stoi(val);
145 return f;
146}
Holds a parsed modification-time filter with comparison operator and day count.
Definition cmd.cpp:42
CmpOp op
Comparison direction.
Definition cmd.cpp:44
int days
Age threshold in days.
Definition cmd.cpp:45
bool active
Whether this filter is enabled.
Definition cmd.cpp:43

References TimeFilter::active, TimeFilter::days, EQ, GT, LT, and TimeFilter::op.

Referenced by main().

Here is the caller graph for this function:

◆ matches_filters()

bool matches_filters ( const fs::directory_entry & entry)

Test a directory entry against all active metadata filters.

Parameters
entryThe filesystem directory entry to check.
Returns
true if the entry passes all active filters, false otherwise.

Definition at line 153 of file cmd.cpp.

153 {
154 std::error_code ec;
155
156 // Type filter
157 if (opts.type_filter != 0) {
158 switch (opts.type_filter) {
159 case 'f':
160 if (!entry.is_regular_file(ec)) return false;
161 break;
162 case 'd':
163 if (!entry.is_directory(ec)) return false;
164 break;
165 case 'l':
166 if (!entry.is_symlink(ec)) return false;
167 break;
168 }
169 }
170
171 // Size filter (only meaningful for regular files)
172 if (opts.size_filter.active) {
173 if (!entry.is_regular_file(ec)) return false;
174 auto sz = entry.file_size(ec);
175 if (ec) return false;
176 switch (opts.size_filter.op) {
177 case CmpOp::GT: if (sz <= opts.size_filter.bytes) return false; break;
178 case CmpOp::LT: if (sz >= opts.size_filter.bytes) return false; break;
179 case CmpOp::EQ: if (sz != opts.size_filter.bytes) return false; break;
180 }
181 }
182
183 // Modification time filter
184 if (opts.mtime_filter.active) {
185 auto ftime = entry.last_write_time(ec);
186 if (ec) return false;
187 auto sctp = std::chrono::clock_cast<std::chrono::system_clock>(ftime);
188 auto now = std::chrono::system_clock::now();
189 auto age = std::chrono::duration_cast<std::chrono::hours>(now - sctp).count() / 24;
190 switch (opts.mtime_filter.op) {
191 case CmpOp::GT: if (age <= opts.mtime_filter.days) return false; break;
192 case CmpOp::LT: if (age >= opts.mtime_filter.days) return false; break;
193 case CmpOp::EQ: if (age != opts.mtime_filter.days) return false; break;
194 }
195 }
196
197 // Permission filter (octal comparison)
198 if (!opts.perm_filter.empty()) {
199 struct stat st;
200 if (stat(entry.path().c_str(), &st) != 0) return false;
201 auto mode = st.st_mode & 07777;
202 auto target = static_cast<mode_t>(std::stoul(opts.perm_filter, nullptr, 8));
203 if (mode != target) return false;
204 }
205
206 // User filter
207 if (!opts.user_filter.empty()) {
208 struct stat st;
209 if (stat(entry.path().c_str(), &st) != 0) return false;
210 struct passwd *pw = getpwuid(st.st_uid);
211 if (!pw || opts.user_filter != pw->pw_name) return false;
212 }
213
214 // Group filter
215 if (!opts.group_filter.empty()) {
216 struct stat st;
217 if (stat(entry.path().c_str(), &st) != 0) return false;
218 struct group *gr = getgrgid(st.st_gid);
219 if (!gr || opts.group_filter != gr->gr_name) return false;
220 }
221
222 return true;
223}
static Options opts
Global runtime options.
Definition cmd.cpp:66

References EQ, GT, LT, and opts.

Referenced by add_directory().

Here is the caller graph for this function:

◆ proc_cmd()

bool proc_cmd ( const std::string & cmd,
std::span< const std::string > text )

Substitute placeholders in a command template and execute the result.

Replaces %%0 (filename), %%1 (full path), %b (stem), %e (extension), and %%2+ (extra args). Supports confirm mode, dry-run, parallel forking, and stop-on-error.

Parameters
cmdThe command template string.
textSpan of strings: text[0] is the matched file path, text[1+] are extras.
Returns
true to continue processing, false to stop (stop-on-error triggered).

Definition at line 412 of file cmd.cpp.

412 {
413 std::string r = cmd;
414 if (!text.empty()) {
415 auto fpath = fs::path(text[0]);
416 auto fname = fpath.filename().string();
417 r = replace_string(r, "%0", fname);
418 r = replace_string(r, "%b", fpath.stem().string());
419 r = replace_string(r, "%e", fpath.extension().string());
420 }
421 for (size_t i = 0; i < text.size(); ++i) {
422 auto placeholder = std::format("%{}", i + 1);
423 if (i == 0 && text[i].find(' ') != std::string::npos)
424 r = replace_string(r, placeholder, std::format("\"{}\"", text[i]));
425 else
426 r = replace_string(r, placeholder, std::string{text[i]});
427 }
428
429 if (opts.confirm) {
430 std::cout << std::format("Execute: {} ? [y/N] ", r);
431 std::string answer;
432 std::getline(std::cin, answer);
433 if (answer != "y" && answer != "Y")
434 return true;
435 }
436
437 if (opts.verbose || opts.dry_run)
438 std::cout << r << "\n";
439
440 if (opts.dry_run) {
441 stats.commands_run++;
442 return true;
443 }
444
445 if (opts.jobs > 1) {
447 if (stop_requested) return false;
448 pid_t pid = fork();
449 if (pid == 0) {
450 execl("/bin/sh", "sh", "-c", r.c_str(), static_cast<char *>(nullptr));
451 _exit(127);
452 } else if (pid > 0) {
453 child_pids.push_back(pid);
454 } else {
455 perror("fork");
456 stats.commands_failed++;
457 return !opts.stop_on_error;
458 }
459 return true;
460 }
461
462 int ret = System(r);
463 stats.commands_run++;
464 if (ret != 0) {
465 stats.commands_failed++;
466 if (opts.stop_on_error) {
467 std::cerr << std::format("Error: command failed (exit {}), stopping.\n", WEXITSTATUS(ret));
468 stop_requested = true;
469 return false;
470 }
471 }
472 return true;
473}
static Stats stats
Global execution statistics.
Definition cmd.cpp:75
std::string replace_string(std::string orig, const std::string &with, const std::string &rep)
Replace all occurrences of a substring within a string.
Definition cmd.cpp:232
static bool stop_requested
Set to true when stop-on-error triggers.
Definition cmd.cpp:77
void wait_for_slot()
Block until a parallel execution slot is available.
Definition cmd.cpp:370
int System(const std::string &command)
Execute a shell command using fork/exec with proper signal handling.
Definition cmd.cpp:314
static std::vector< pid_t > child_pids
PIDs of outstanding child processes (parallel mode).
Definition cmd.cpp:76

References child_pids, opts, replace_string(), stats, stop_requested, System(), and wait_for_slot().

Referenced by add_directory().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ wait_for_slot()

void wait_for_slot ( )

Block until a parallel execution slot is available.

Waits for any outstanding child process to finish, updates stats, and sets stop_requested if stop-on-error is enabled and a child failed.

Definition at line 370 of file cmd.cpp.

370 {
371 while (static_cast<int>(child_pids.size()) >= opts.jobs) {
372 int status;
373 pid_t pid = waitpid(-1, &status, 0);
374 if (pid > 0) {
375 std::erase(child_pids, pid);
376 stats.commands_run++;
377 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
378 stats.commands_failed++;
379 if (opts.stop_on_error)
380 stop_requested = true;
381 }
382 }
383 }
384}

References child_pids, opts, stats, and stop_requested.

Referenced by proc_cmd().

Here is the caller graph for this function:

◆ wait_all()

void wait_all ( )

Wait for all outstanding child processes to finish.

Called at the end of execution in parallel mode to drain the process pool.

Definition at line 390 of file cmd.cpp.

390 {
391 while (!child_pids.empty()) {
392 int status;
393 pid_t pid = waitpid(-1, &status, 0);
394 if (pid > 0) {
395 std::erase(child_pids, pid);
396 stats.commands_run++;
397 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
398 stats.commands_failed++;
399 }
400 }
401}

References child_pids, and stats.

Referenced by main().

Here is the caller graph for this function:

◆ replace_string()

std::string replace_string ( std::string orig,
const std::string & with,
const std::string & rep )

Replace all occurrences of a substring within a string.

Parameters
origThe original string.
withThe substring to search for.
repThe replacement text.
Returns
A new string with all occurrences replaced.

Definition at line 232 of file cmd.cpp.

232 {
233 size_t pos = 0;
234 while ((pos = orig.find(with, pos)) != std::string::npos) {
235 orig.replace(pos, with.length(), rep);
236 pos += rep.length();
237 }
238 return orig;
239}

Referenced by proc_cmd().

Here is the caller graph for this function:

◆ add_directory()

void add_directory ( const fs::path & path,
const std::string & cmd,
const std::string & regex_str,
std::vector< std::string > & args,
int depth )

Recursively walk a directory, match entries against a regex and filters, and run commands.

Parameters
pathThe directory to scan.
cmdThe command template containing placeholders.
regex_strECMAScript regex matched against each entry's full path.
argsMutable argument vector; args[0] is overwritten with the matched path.
depthCurrent recursion depth (0 at the root call).

Definition at line 249 of file cmd.cpp.

249 {
250 if (opts.max_depth >= 0 && depth > opts.max_depth)
251 return;
252 if (stop_requested) return;
253
254 std::error_code ec;
255 auto dir = fs::directory_iterator(path, fs::directory_options::skip_permission_denied, ec);
256 if (ec) {
257 std::cerr << std::format("Error: could not open directory: {}\n", path.string());
258 exit(EXIT_FAILURE);
259 }
260
261 for (const auto &entry : dir) {
262 if (stop_requested) return;
263 auto filename = entry.path().filename().string();
264 if (!opts.hidden && filename.starts_with('.'))
265 continue;
266
267 // Exclude pattern check
268 if (!opts.exclude_pattern.empty()) {
269 std::regex excl(opts.exclude_pattern, std::regex::ECMAScript);
270 if (std::regex_search(filename, excl))
271 continue;
272 }
273
274 if (entry.is_directory(ec)) {
275 // If type filter is 'd', also match directories against regex
276 if (opts.type_filter == 'd') {
277 std::regex ex(regex_str);
278 auto fullpath = entry.path().string();
279 if (std::regex_search(fullpath, ex) && matches_filters(entry)) {
280 stats.files_matched++;
281 args[0] = fullpath;
282 if (!proc_cmd(cmd, args)) return;
283 }
284 }
285 add_directory(entry.path(), cmd, regex_str, args, depth + 1);
286 } else if (entry.is_symlink(ec) && opts.type_filter == 'l') {
287 std::regex ex(regex_str);
288 auto fullpath = entry.path().string();
289 if (std::regex_search(fullpath, ex) && matches_filters(entry)) {
290 stats.files_matched++;
291 args[0] = fullpath;
292 if (!proc_cmd(cmd, args)) return;
293 }
294 } else if (entry.is_regular_file(ec) || (entry.is_symlink(ec) && opts.type_filter == 0)) {
295 std::regex ex(regex_str);
296 auto fullpath = entry.path().string();
297 if (std::regex_search(fullpath, ex) && matches_filters(entry)) {
298 stats.files_matched++;
299 args[0] = fullpath;
300 if (!proc_cmd(cmd, args)) return;
301 }
302 }
303 }
304}
bool proc_cmd(const std::string &cmd, std::span< const std::string > text)
Substitute placeholders in a command template and execute the result.
Definition cmd.cpp:412
bool matches_filters(const fs::directory_entry &entry)
Test a directory entry against all active metadata filters.
Definition cmd.cpp:153
void add_directory(const fs::path &path, const std::string &cmd, const std::string &regex_str, std::vector< std::string > &args, int depth)
Recursively walk a directory, match entries against a regex and filters, and run commands.
Definition cmd.cpp:249

References add_directory(), matches_filters(), opts, proc_cmd(), stats, and stop_requested.

Referenced by add_directory(), and main().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ System()

int System ( const std::string & command)

Execute a shell command using fork/exec with proper signal handling.

Blocks SIGCHLD and ignores SIGINT/SIGQUIT in the parent process to prevent interrupted batch operations. Restores all signal masks after the child exits.

Parameters
commandThe shell command string to execute via /bin/sh -c.
Returns
The child's exit status from waitpid, or -1 on fork failure.

Definition at line 314 of file cmd.cpp.

314 {
315 sigset_t bmask, omask;
316 struct sigaction sa_ignore, sa_oquit, sa_origint, sa_default;
317 pid_t id;
318 int status, serrno;
319
320 if (command.empty())
321 return System(":") == 0;
322
323 sigemptyset(&bmask);
324 sigaddset(&bmask, SIGCHLD);
325 sigprocmask(SIG_BLOCK, &bmask, &omask);
326 sa_ignore.sa_handler = SIG_IGN;
327 sa_ignore.sa_flags = 0;
328 sigemptyset(&sa_ignore.sa_mask);
329 sigaction(SIGINT, &sa_ignore, &sa_origint);
330 sigaction(SIGQUIT, &sa_ignore, &sa_oquit);
331
332 switch ((id = fork())) {
333 case -1:
334 status = -1;
335 break;
336 case 0:
337 sa_default.sa_handler = SIG_DFL;
338 sa_default.sa_flags = 0;
339 sigemptyset(&sa_default.sa_mask);
340 if (sa_origint.sa_handler != SIG_IGN)
341 sigaction(SIGINT, &sa_default, NULL);
342 if (sa_oquit.sa_handler != SIG_IGN)
343 sigaction(SIGQUIT, &sa_default, NULL);
344
345 execl("/bin/sh", "sh", "-c", command.c_str(), static_cast<char *>(nullptr));
346 _exit(127);
347 break;
348 default:
349 while (waitpid(id, &status, 0) == -1) {
350 if (errno != EINTR) {
351 status = -1;
352 break;
353 }
354 }
355 break;
356 }
357 serrno = errno;
358 sigprocmask(SIG_SETMASK, &omask, NULL);
359 sigaction(SIGINT, &sa_origint, NULL);
360 sigaction(SIGQUIT, &sa_oquit, NULL);
361 errno = serrno;
362 return status;
363}

References System().

Referenced by proc_cmd(), and System().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ print_help()

void print_help ( const char * prog)

Print usage information to stdout.

Parameters
progThe program name (argv[0]).

Definition at line 479 of file cmd.cpp.

479 {
480 std::cout << std::format(
481 "usage: {} [options] path \"command %1 [%2 %3..]\" regex [extra_args..]\n\n"
482 "Recursively find files matching regex and run command for each.\n\n"
483 "placeholders:\n"
484 " %0 filename only (no path)\n"
485 " %1 full path to matched file\n"
486 " %2+ extra arguments from command line\n"
487 " %b basename without extension\n"
488 " %e file extension (including dot)\n\n"
489 "options:\n"
490 " -n, --dry-run dry-run, print commands without executing\n"
491 " -v, --verbose verbose, print each command before running\n"
492 " -a, --all include hidden files/directories\n"
493 " -d, --depth N max recursion depth (0 = current dir only)\n"
494 " -s, --size SIZE filter by size: +10M (>10MB), -1K (<1KB),\n"
495 " 4096 (exactly 4096 bytes). Suffixes: K, M, G\n"
496 " -m, --mtime DAYS filter by modification time: +7 (older than 7 days),\n"
497 " -1 (modified within last day), 3 (exactly 3 days)\n"
498 " -p, --perm MODE filter by permissions (octal), e.g. 755\n"
499 " -u, --user USER filter by owner username\n"
500 " -g, --group GROUP filter by group name\n"
501 " -t, --type TYPE filter by type: f (file), d (directory), l (symlink)\n"
502 " -x, --exclude REGEX exclude files/directories matching REGEX\n"
503 " -e, --stop-on-error stop on first command failure\n"
504 " -c, --confirm prompt for confirmation before each command\n"
505 " -j, --jobs N run N commands in parallel (default: 1)\n"
506 " -h, --help show this help\n",
507 prog);
508}

Referenced by main().

Here is the caller graph for this function:

◆ main()

int main ( int argc,
char ** argv )

Program entry point.

Parses command-line arguments via argz, configures options, validates positional arguments and placeholder consistency, runs directory traversal, waits for parallel children, and prints a summary.

Parameters
argcArgument count.
argvArgument vector.
Returns
EXIT_SUCCESS on success, EXIT_FAILURE if any command failed or input was invalid.

Definition at line 519 of file cmd.cpp.

519 {
520 Argz<std::string> argz(argc, argv);
521 argz.addOptionSingle('n', "dry-run mode")
522 .addOptionDouble('N', "dry-run", "dry-run mode")
523 .addOptionSingle('v', "verbose output")
524 .addOptionDouble('V', "verbose", "verbose output")
525 .addOptionSingle('a', "include hidden files")
526 .addOptionDouble('A', "all", "include hidden files")
527 .addOptionSingleValue('d', "max depth")
528 .addOptionDoubleValue('D', "depth", "max depth")
529 .addOptionSingleValue('s', "size filter")
530 .addOptionDoubleValue('S', "size", "size filter")
531 .addOptionSingleValue('m', "modification time filter")
532 .addOptionDoubleValue('M', "mtime", "modification time filter")
533 .addOptionSingleValue('p', "permission filter")
534 .addOptionDoubleValue('P', "perm", "permission filter")
535 .addOptionSingleValue('u', "user filter")
536 .addOptionDoubleValue('U', "user", "user filter")
537 .addOptionSingleValue('g', "group filter")
538 .addOptionDoubleValue('G', "group", "group filter")
539 .addOptionSingleValue('t', "type filter")
540 .addOptionDoubleValue('T', "type", "type filter")
541 .addOptionSingleValue('x', "exclude pattern")
542 .addOptionDoubleValue('X', "exclude", "exclude pattern")
543 .addOptionSingle('e', "stop on error")
544 .addOptionDouble('E', "stop-on-error", "stop on error")
545 .addOptionSingle('c', "confirm mode")
546 .addOptionDouble('C', "confirm", "confirm mode")
547 .addOptionSingleValue('j', "parallel jobs")
548 .addOptionDoubleValue('J', "jobs", "parallel jobs")
549 .addOptionSingle('h', "show help")
550 .addOptionDouble('H', "help", "show help");
551
552 std::vector<std::string> positional;
553
554 try {
556 int ret;
557 while ((ret = argz.proc(arg)) != -1) {
558 switch (ret) {
559 case 'n':
560 case 'N':
561 opts.dry_run = true;
562 break;
563 case 'v':
564 case 'V':
565 opts.verbose = true;
566 break;
567 case 'a':
568 case 'A':
569 opts.hidden = true;
570 break;
571 case 'd':
572 case 'D':
573 opts.max_depth = std::stoi(arg.arg_value);
574 break;
575 case 's':
576 case 'S':
577 opts.size_filter = parse_size_filter(arg.arg_value);
578 break;
579 case 'm':
580 case 'M':
581 opts.mtime_filter = parse_time_filter(arg.arg_value);
582 break;
583 case 'p':
584 case 'P':
585 opts.perm_filter = arg.arg_value;
586 break;
587 case 'u':
588 case 'U':
589 opts.user_filter = arg.arg_value;
590 break;
591 case 'g':
592 case 'G':
593 opts.group_filter = arg.arg_value;
594 break;
595 case 't':
596 case 'T':
597 if (arg.arg_value == "f" || arg.arg_value == "d" || arg.arg_value == "l") {
598 opts.type_filter = arg.arg_value[0];
599 } else {
600 std::cerr << std::format("Error: invalid type '{}'. Use f (file), d (directory), or l (symlink).\n", arg.arg_value);
601 return EXIT_FAILURE;
602 }
603 break;
604 case 'x':
605 case 'X':
606 opts.exclude_pattern = arg.arg_value;
607 break;
608 case 'e':
609 case 'E':
610 opts.stop_on_error = true;
611 break;
612 case 'c':
613 case 'C':
614 opts.confirm = true;
615 break;
616 case 'j':
617 case 'J':
618 opts.jobs = std::stoi(arg.arg_value);
619 if (opts.jobs < 1) opts.jobs = 1;
620 break;
621 case 'h':
622 case 'H':
623 print_help(argv[0]);
624 return 0;
625 case '-':
626 positional.push_back(arg.arg_value);
627 break;
628 }
629 }
630 } catch (const ArgException<std::string> &e) {
631 std::cerr << std::format("Error: {}\n", e.text());
632 return EXIT_FAILURE;
633 }
634
635 if (positional.size() < 3) {
636 std::cerr << "Error: at least three positional arguments required.\n";
637 print_help(argv[0]);
638 return EXIT_FAILURE;
639 }
640
641 const auto &path = positional[0];
642 const auto &input = positional[1];
643 const auto &regex_str = positional[2];
644
645 size_t index = 2;
646 std::vector<std::string> args{"filename"};
647 for (size_t i = 3; i < positional.size(); ++i) {
648 if (input.find(std::format("%{}", index)) == std::string::npos) {
649 std::cerr << std::format("Error: command has no placeholder %{} for extra argument \"{}\"\n", index, positional[i]);
650 return EXIT_FAILURE;
651 }
652 args.push_back(positional[i]);
653 ++index;
654 }
655
656 add_directory(path, input, regex_str, args, 0);
657
658 if (opts.jobs > 1)
659 wait_all();
660
661 if (opts.verbose || opts.dry_run || stats.commands_failed > 0) {
662 std::cerr << std::format("\nSummary: {} matched, {} run, {} failed\n",
663 stats.files_matched, stats.commands_run, stats.commands_failed);
664 }
665
666 return stats.commands_failed > 0 ? EXIT_FAILURE : 0;
667}
String text() const
Definition argz.hpp:84
Definition argz.hpp:91
void print_help(const char *prog)
Print usage information to stdout.
Definition cmd.cpp:479
void wait_all()
Wait for all outstanding child processes to finish.
Definition cmd.cpp:390
TimeFilter parse_time_filter(const std::string &s)
Parse a time filter string into a TimeFilter.
Definition cmd.cpp:131
SizeFilter parse_size_filter(const std::string &s)
Parse a size filter string into a SizeFilter.
Definition cmd.cpp:96
String arg_value
Definition argz.hpp:41

References add_directory(), Argz< String >::addOptionSingle(), Argument< String >::arg_value, opts, parse_size_filter(), parse_time_filter(), print_help(), Argz< String >::proc(), stats, ArgException< String >::text(), and wait_all().

Here is the call graph for this function:

Variable Documentation

◆ opts

Options opts
static

Global runtime options.

Definition at line 66 of file cmd.cpp.

Referenced by add_directory(), main(), matches_filters(), proc_cmd(), and wait_for_slot().

◆ stats

Stats stats
static

Global execution statistics.

Definition at line 75 of file cmd.cpp.

Referenced by add_directory(), main(), proc_cmd(), wait_all(), and wait_for_slot().

◆ child_pids

std::vector<pid_t> child_pids
static

PIDs of outstanding child processes (parallel mode).

Definition at line 76 of file cmd.cpp.

Referenced by proc_cmd(), wait_all(), and wait_for_slot().

◆ stop_requested

bool stop_requested = false
static

Set to true when stop-on-error triggers.

Definition at line 77 of file cmd.cpp.

Referenced by add_directory(), proc_cmd(), and wait_for_slot().