pax_global_header00006660000000000000000000000064151514716210014515gustar00rootroot0000000000000052 comment=4020c3177e9fab0fc7e4dd547f0668282dc1d9a9 wheres/000077500000000000000000000000001515147162100123565ustar00rootroot00000000000000wheres/.clang-format000066400000000000000000000167261515147162100147450ustar00rootroot00000000000000--- Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveAssignments: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveBitFields: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveDeclarations: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveMacros: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveShortCaseStatements: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCaseArrows: false AlignCaseColons: false AlignConsecutiveTableGenBreakingDAGArgColons: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveTableGenCondOperatorColons: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveTableGenDefinitionColons: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: Kind: Never OverEmptyLines: 0 AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowBreakBeforeNoexceptSpecifier: Never AllowShortBlocksOnASingleLine: Never AllowShortCaseExpressionOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortCompoundRequirementOnASingleLine: true AllowShortEnumsOnASingleLine: true AllowShortFunctionsOnASingleLine: false AllowShortIfStatementsOnASingleLine: AllIfsAndElse AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakBeforeMultilineStrings: false AttributeMacros: - __capability BinPackArguments: true BinPackParameters: true BitFieldColonSpacing: Both BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakAdjacentStringLiterals: true BreakAfterAttributes: Leave BreakAfterJavaFieldAnnotations: false BreakAfterReturnType: None BreakArrays: true BreakBeforeBinaryOperators: NonAssignment BreakBeforeConceptDeclarations: Always BreakBeforeBraces: Attach BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakFunctionDefinitionParameters: false BreakInheritanceList: BeforeColon BreakStringLiterals: true BreakTemplateDeclarations: MultiLine ColumnLimit: 120 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 CaseSensitive: false - Regex: '.*' Priority: 1 SortPriority: 0 CaseSensitive: false IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentAccessModifiers: false IndentCaseBlocks: false IndentCaseLabels: false IndentExternBlock: AfterExternBlock IndentGotoLabels: true IndentPPDirectives: None IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false InsertBraces: false InsertNewlineAtEOF: false InsertTrailingCommas: None IntegerLiteralSeparator: Binary: 0 BinaryMinDigits: 0 Decimal: 0 DecimalMinDigits: 0 Hex: 0 HexMinDigits: 0 JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLines: AtEndOfFile: false AtStartOfBlock: true AtStartOfFile: true LambdaBodyIndentation: Signature LineEnding: DeriveLF MacroBlockBegin: '' MacroBlockEnd: '' MainIncludeChar: Quote MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PackConstructorInitializers: BinPack PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakOpenParenthesis: 0 PenaltyBreakScopeResolution: 500 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyIndentedWhitespace: 0 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right PPIndentWidth: -1 QualifierAlignment: Leave ReferenceAlignment: Pointer ReflowComments: true RemoveBracesLLVM: false RemoveParentheses: Leave RemoveSemicolon: false RequiresClausePosition: OwnLine RequiresExpressionIndentation: OuterScope SeparateDefinitionBlocks: Leave ShortNamespaceLines: 1 SkipMacroDefinitionBody: false SortIncludes: CaseSensitive SortJavaStaticImport: Before SortUsingDeclarations: LexicographicNumeric SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceAroundPointerQualifiers: Default SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeJsonColon: false SpaceBeforeParens: ControlStatements SpaceBeforeParensOptions: AfterControlStatements: true AfterForeachMacros: true AfterFunctionDefinitionName: false AfterFunctionDeclarationName: false AfterIfMacros: true AfterOverloadedOperator: false AfterPlacementOperator: true AfterRequiresInClause: false AfterRequiresInExpression: false BeforeNonEmptyParentheses: false SpaceBeforeRangeBasedForLoopColon: true SpaceBeforeSquareBrackets: false SpaceInEmptyBlock: false SpacesBeforeTrailingComments: 1 SpacesInAngles: Never SpacesInContainerLiterals: true SpacesInLineCommentPrefix: Minimum: 1 Maximum: -1 SpacesInParens: Never SpacesInParensOptions: ExceptDoubleParentheses: false InCStyleCasts: false InConditionalStatements: false InEmptyParentheses: false Other: false SpacesInSquareBrackets: false Standard: Latest StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TableGenBreakInsideDAGArg: DontBreak TabWidth: 4 UseTab: Never VerilogBreakBetweenInstancePorts: true WhitespaceSensitiveMacros: - BOOST_PP_STRINGIZE - CF_SWIFT_NAME - NS_SWIFT_NAME - PP_STRINGIZE - STRINGIZE ... wheres/.gitignore000066400000000000000000000000471515147162100143470ustar00rootroot00000000000000.* /wheres !.gitignore !.clang-format wheres/Makefile000066400000000000000000000003241515147162100140150ustar00rootroot00000000000000PREFIX=~/.local G= O=-Ofast CFLAGS=$(O) $(G) -Wall -Wextra -Werror wheres: wheres.c cc $(CFLAGS) $^ -o $@ install: wheres cp wheres $(PREFIX)/bin uninstall: rm -f $(PREFIX)/bin/wheres clean: rm -f wheres wheres/README.md000066400000000000000000000037471515147162100136500ustar00rootroot00000000000000# Where's: A simple file finder `wheres` is a simple tool for finding where a file or directory is. `wheres` is like if `find` had a sane interface and worked better. |`find` | `wheres` | |-----------------------------------|-----------------------------------------| |`find -name foo.c` | `wheres foo.c` | |`find /foo /baz -name foo.c 2>/dev/null` | `wheres /foo /baz foo.c` | |`find -name foo.c -or -name baz.c` | `wheres foo.c baz.c` | |`find -iname 'readme*'` | `wheres -i 'readme*'` | |`find -regex '.*/foo\(\.c\)?'` | `wheres 'foo*' | grep '.*/foo\(\.c\)?'` | ## Some differences ### No error messages `wheres` does not print file access errors, it silently ignores directories whose contents can't be read. For example, searching inside `/`, will not spam the console with useless access errors when not run as root. ### Default directory `wheres` operates in the current working directory by default, which can be overridden using the `-d` flag to set the working directory. You do not need to specify the directory first. ### Patterns are the main argument Positional arguments to `wheres` act as globbing patterns and if _any_ pattern matches a filename, it will be printed, rather than `find`'s default logic that requires _all_ patterns to match a filename. There is no `-or`/`-and` flags for complex boolean logic. ## Usage * `--help,-h`: print a help message * `--dir,-d `: search in a given directory * `--ignorecase,-i`: search case-insensitively * `--type,-t `: search for (f)iles, (d)irectories, (l)inks, (p)ipes, (s)ockets, (b)lock devices, (c)haracter devices * `--nul,-0`: print a nul byte instead of a newline after each match Positional arguments are treated as patterns to search for, unless they contain a `/` or are one of the literal values `.`, `..`, `~`, in which case they're treated as paths to search from. wheres/argparse.h000066400000000000000000000051431515147162100143360ustar00rootroot00000000000000#include #include #include #include #ifndef streq #define streq(str, target) (strcmp(str, target) == 0) #endif #ifndef startswith #define startswith(str, target) (strncmp(str, target, strlen(target)) == 0) #endif #define USAGE_ERROR(program_name, usage, ...) \ ({ \ printf("%s: ", program_name); \ printf(__VA_ARGS__); \ printf("\n\n%s", usage); \ exit(1); \ }) static const char *pop_str_arg(const char *program_name, const char *usage, const char *flag, char shortflag, int *i, int argc, char *argv[]) { if (startswith(argv[*i], flag)) { if (argv[*i][strlen(flag)] == '\0') { if (*i + 1 >= argc) USAGE_ERROR(program_name, usage, "No argument provided for %s", flag); const char *ret = argv[*i + 1]; *i += 2; return ret; } else if (argv[*i][strlen(flag)] == '=') { const char *ret = &argv[*i][strlen(flag) + 1]; *i += 1; return ret; } } else if (argv[*i][0] == '-' && argv[*i][1] == shortflag) { if (argv[*i][2] == '=') { const char *ret = &argv[*i][3]; *i += 1; return ret; } else if (argv[*i][2] == '\0') { if (*i + 1 >= argc) USAGE_ERROR(program_name, usage, "No argument provided for -%c", shortflag); const char *ret = argv[*i + 1]; *i += 2; return ret; } } return NULL; } static bool pop_bool_arg(const char *flag, char shortflag, int *i, int argc, char *argv[]) { if (streq(argv[*i], flag)) { memmove(&argv[*i], &argv[*i + 1], sizeof(char * [argc - *i - 1])); *i += 1; return true; } else if (argv[*i][0] == '-' && argv[*i][1] != '-') { if (argv[*i][1] == shortflag && argv[*i][2] == '\0') { *i += 1; return true; } char *c = strchr(argv[*i], shortflag); if (c) { memmove(c, c + 1, strlen(c)); return true; } } return false; } wheres/wheres.c000066400000000000000000000105321515147162100140200ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include #include #include "argparse.h" const char *program_name = "wheres"; static const char *USAGE = "Usage:\n" " patterns or paths...: patterns to search for or paths to search for them in\n" " --help,-h: print this help message\n" " --dir,-d : search in a given directory\n" " --ignorecase,-i: search case-insensitively\n" " --type,-t : search for (f)iles, (d)irectories, (l)inks, (p)ipes, (s)ockets, (b)lock " "devices, (c)haracter devices\n" " --nul,-0: print a nul byte instead of a newline after each match\n"; #ifndef streq #define streq(str, target) (strcmp(str, target) == 0) #endif static int pat_flags = FNM_PATHNAME; static const char *type = NULL; static char print_separator = '\n'; static inline bool matches_any_pattern(const char *name, int patc, const char *patv[patc]) { if (patc == 0) return true; for (int i = 0; i < patc; i++) { if (fnmatch(patv[i], name, pat_flags) == 0) { return true; } } return false; } static inline bool is_valid_type(struct dirent *entry) { if (type == NULL) return true; switch (entry->d_type) { case DT_FIFO: return strchr(type, 'p') != NULL; case DT_CHR: return strchr(type, 'c') != NULL; case DT_DIR: return strchr(type, 'd') != NULL; case DT_BLK: return strchr(type, 'b') != NULL; case DT_REG: return strchr(type, 'f') != NULL; case DT_LNK: return strchr(type, 'l') != NULL; case DT_SOCK: return strchr(type, 's') != NULL; case DT_WHT: return strchr(type, 'w') != NULL; default: return false; } } void find(const char *path, int patc, const char *patv[patc]) { DIR *dir = opendir(path); if (!dir) return; struct dirent *entry; while ((entry = readdir(dir)) != NULL) { const char *name = entry->d_name; if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { // Skip '.' and '..' continue; } if (is_valid_type(entry) && matches_any_pattern(name, patc, patv)) { dprintf(STDOUT_FILENO, "%s/%s%c", path, name, print_separator); } if (entry->d_type == DT_DIR) { char new_path[PATH_MAX]; snprintf(new_path, sizeof(new_path), "%s/%s", streq(path, "/") ? "" : path, name); find(new_path, patc, patv); } } closedir(dir); } int main(int argc, char *argv[]) { program_name = argv[0]; int num_dirs = 0; // This array is guaranteed to be big enough, since argc >= 1, and each argument // adds at most one search dir. const char *dirs[argc] = {}; int num_pats = 0; const char *pats[argc] = {}; for (int i = 1; i < argc;) { const char *dir; const char *new_type; if (streq(argv[i], "--")) { break; } else if ((dir = pop_str_arg(program_name, USAGE, "--dir", 'd', &i, argc, argv))) { dirs[num_dirs++] = dir; } else if (pop_bool_arg("--ignorecase", 'i', &i, argc, argv)) { pat_flags |= FNM_CASEFOLD; } else if (pop_bool_arg("--help", 'h', &i, argc, argv)) { printf("%s - find files and directories\n\n%s", program_name, USAGE); return 0; } else if ((new_type = pop_str_arg(program_name, USAGE, "--type", 't', &i, argc, argv)) != NULL) { type = new_type; continue; } else if (pop_bool_arg("--nul", '0', &i, argc, argv)) { print_separator = '\0'; } else if (strchr(argv[i], '/') || streq(argv[i], "..") || streq(argv[i], ".") || streq(argv[i], "~")) { dirs[num_dirs++] = argv[i]; i += 1; } else if (argv[i][0] == '-') { USAGE_ERROR(program_name, USAGE, "Unrecognized argument: %s", argv[i]); } else { pats[num_pats++] = argv[i]; i += 1; } } if (num_dirs == 0) { dirs[num_dirs++] = "."; } for (int i = 0; i < num_dirs; i++) { static char real[PATH_MAX]; char *path = realpath(dirs[i], real); if (!path) { fprintf(stderr, "Not a valid path: %s", dirs[i]); exit(1); } find(path, num_pats, pats); if (path != real) free(path); } return 0; }