From d560d7ec4bf87a9f9dd2f7135489b26db5f9fb4b Mon Sep 17 00:00:00 2001 From: Lily Chung Date: Fri, 20 Sep 2019 01:38:57 -0700 Subject: Add the -0 option to null-terminate filenames. --- README.md | 4 ++++ sor | 32 +++++++++++++++++++++++++++++--- sor.1 | 10 ++++++++++ walk.1 | 9 +++++++++ walk.c | 29 ++++++++++++++++++++++------- 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1d913ac..fee7b76 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ you can say walk | grep foo | sor 'test -f' +If your filenames might contain newlines, you can say + + walk -0 | grep -z foo | sor -0 'test -f' + [find]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html diff --git a/sor b/sor index 0960db8..f5d18a0 100755 --- a/sor +++ b/sor @@ -24,6 +24,7 @@ For each line from standard input, evaluate the specified SNIPPETs under Bash with the line as the argument. Print the line if any snippet exits with status 0. + -0, --null separate input and output filenames by null --help display this help and exit EOF } @@ -32,18 +33,43 @@ ask_for_help() { echo "Try 'sor --help' for more information." } -if ! temp=$(getopt -s bash -n sor -o '' -l help -- "$@"); then +read_filename() { + local null_terminate="$1" + local target_var="$2" + if [ "$null_terminate" = "true" ]; then + IFS= read -r -d '' "$target_var" + else + read "$target_var" + fi +} + +print_filename() { + local null_terminate="$1" + local filename="$2" + if [ "$null_terminate" = "true" ]; then + printf '%s\0' "$filename" + else + echo "$filename" + fi +} + +if ! temp=$(getopt -s bash -n sor -o '0' -l null,help -- "$@"); then ask_for_help >&2 exit 1 fi eval set -- "$temp" unset temp +null_terminate="false" while true; do case "$1" in --help) help exit 0 ;; + -0|--null) + shift + null_terminate="true" + ;; --) shift break @@ -55,10 +81,10 @@ while true; do esac done -while read file; do +while read_filename "$null_terminate" file; do for test in "$@"; do if eval "$test \"$file\""; then - echo "$file" + print_filename "$null_terminate" "$file" continue 2 fi done diff --git a/sor.1 b/sor.1 index ab69baf..ff311ca 100644 --- a/sor.1 +++ b/sor.1 @@ -16,12 +16,22 @@ sor \- combine shell predicates with logical or .SH SYNOPSIS .B sor +.RI [\| OPTION ...\] .IR SNIPPET ... .SH DESCRIPTION Read paths from standard input. For each path, evaluate each argument as a .BR bash (1) snippet with the path as the first argument. Print the path to standard output if any of the snippets exits with status 0. +.SH OPTIONS +.TP +\fB-0, --null\fP +Filenames are terminated using the null character \fB\\0\fP instead of +newlines, for both input and output. +Suitable for use with \fBwalk -0\fP. +.TP +\fB--help\fP +Print a summary of the usage of \fBsor\fP and exit. .SH EXAMPLE .EX walk /etc | sor 'test -f' diff --git a/walk.1 b/walk.1 index 8fab5ea..da2510f 100644 --- a/walk.1 +++ b/walk.1 @@ -16,12 +16,21 @@ walk \- walk file system .SH SYNOPSIS .B walk +.RI [\| OPTION ...\] .RI [\| DIRECTORY ...\] .SH DESCRIPTION Recursively list the specified .I DIRECTORY (or directories). Recursively list the current directory if no directory is specified. +.SH OPTIONS +.TP +\fB-0, --null\fP +Terminate output filenames using the null character \fB\\0\fP instead of newlines. +Suitable for use with \fBsor -0\fP. +.TP +\fB--help\fP +Print a summary of the usage of \fBwalk\fP and exit. .SH "EXIT STATUS" .BR walk 's exit status is a bitmask indicating errors that occurred. diff --git a/walk.c b/walk.c index d984002..a3a3a0a 100644 --- a/walk.c +++ b/walk.c @@ -21,11 +21,12 @@ #include #include -static const char SHORT_USAGE[] = "Usage: walk [DIRECTORY...]\n"; +static const char SHORT_USAGE[] = "Usage: walk [OPTION...] [DIRECTORY...]\n"; static const char HELP[] = "Recursively walk the specified directories (or current directory, if none is\n" "specified.\n\n" + " -0, --null separate filenames by a null character\n" " --help display this help and exit\n"; static const char ASK_FOR_HELP[] = "Try 'walk --help' for more information.\n"; @@ -55,10 +56,19 @@ static void strcpy3(char *dest, const char *s1, const char *s2, const char *s3) stpcpy(stpcpy(stpcpy(dest, s1), s2), s3); } +static void put_filename(const char *filename, bool null_terminate) { + if (null_terminate) { + fputs(filename, stdout); + fputc(0, stdout); + } else { + puts(filename); + } +} + // Walks the directory named dirname, printing the names of all files it // contains (but not the name of the directory itself). Returns 2 if dirname is // not a directory and 1 if another error occurs. -static int walk(const char dirname[]) +static int walk(const char dirname[], bool null_terminate) { DIR *const dir = opendir(dirname); if (!dir) { @@ -78,11 +88,11 @@ static int walk(const char dirname[]) strlen(dirname) + 1 + strlen(f->d_name) + 1); strcpy3(filename, dirname, "/", f->d_name); // TODO(bbaren@google.com): Emulate Plan 9's cleanname(3). - puts(filename); + put_filename(filename, null_terminate); // Walk the file if we can successfully open it as a directory. // Don't worry about it if it's not one (walk(filename) == 2). if ((f->d_type == DT_DIR || f->d_type == DT_UNKNOWN) - && walk(filename) == 1) + && walk(filename, null_terminate) == 1) r = 1; } if (errno) { // from readdir @@ -101,10 +111,12 @@ int main(const int argc, char *const argv[]) { static const struct option long_options[] = { {"help", no_argument, NULL, 'h'}, + {"null", no_argument, NULL, '0'}, {NULL, 0, NULL, 0}, }; + bool null_terminate = false; while (true) { - const int c = getopt_long(argc, argv, "", long_options, NULL); + const int c = getopt_long(argc, argv, "0", long_options, NULL); if (c == -1) break; switch (c) { @@ -112,6 +124,9 @@ int main(const int argc, char *const argv[]) fputs(SHORT_USAGE, stdout); fputs(HELP, stdout); return 0; + case '0': + null_terminate = true; + break; case '?': fputs(ASK_FOR_HELP, stderr); return 1; @@ -125,8 +140,8 @@ int main(const int argc, char *const argv[]) const char *const *const dirs = argc == optind ? JUST_CURRENT_DIRECTORY : (const char *const *)argv + optind; for (int i = 0; dirs[i]; ++i) { - puts(dirs[i]); - r |= walk(dirs[i]); + put_filename(dirs[i], null_terminate); + r |= walk(dirs[i], null_terminate); } return r; } -- cgit v1.2.3