aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rwxr-xr-xsor32
-rw-r--r--sor.110
-rw-r--r--walk.19
-rw-r--r--walk.c29
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 <dirent.h>
#include <getopt.h>
-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;
}