Cleaned up the documentation and code a little.

This commit is contained in:
Bruce Hill 2019-02-15 01:57:15 -08:00
parent 69eca0eb16
commit fc2fefd049
2 changed files with 50 additions and 31 deletions

View File

@ -1,25 +1,40 @@
# arg - A simple command line argument parser # arg - A simple command line argument parser
This is a simple tool with a simple job: figure out the value of a single This is a simple tool with a simple job: figure out the value of a single
command line argument. Tools like `getopt` and `getopts` are much too command line argument. Tools like `getopt` and `getopts` are unnecessarily
complicated. It's much nicer to be able to write shell scripts like this: complex and difficult to use, while also not supporting common use cases well.
Parsing an argument in a shell script shouldn't require a tutorial or dozen
lines of shell code!
To quote the `getopt` manpage:
> Each shellscript has to carry complex code to parse arguments halfway
> correcty (like the example presented here). A better getopt-like tool would
> move much of the complexity into the tool and keep the client shell scripts
> simpler.
## Usage
Simply run: `arg <flag> <extra args...>`. `<flag>` can be anything, like `-f`,
`-flag`, `--flag`, or `flag`. If the flag occurs among the rest of the
command line arguments, `arg` will print the flag's value (if any) and exit
successfully, otherwise, it will fail (exit status 1). In a shell script, this
would look like:
``` ```
#!/bin/sh #!/bin/sh
dir=`arg --dir "$@" || arg -d "$@" || echo "$HOME/Downloads"` dir=`arg --dir "$@" || arg -d "$@" || echo "$HOME/Downloads"`
if ! server=`arg --server "$@" || arg -s "$@"`; then
echo "No server provided :(" && exit 1
fi
if arg --verbose "$@" || arg -v "$@"; then if arg --verbose "$@" || arg -v "$@"; then
echo "Downloading to $dir" echo "Downloading to $dir"
fi fi
cd $path && curl -O example.com/file.zip curl "$server/file.zip" > "$dir/file.zip"
``` ```
## Usage The flag's value may be of the form `<flag>=<value>` or `<flag> <value>`.
The following forms are accepted: `arg -f ...`, `arg -flag ...`, or `arg --flag Single-letter flags will also match when grouped with other single letter
...`. If `arg` finds the first given flag among the rest of the command line flags, but will not have a value (e.g. `arg -b -abc foo` will succeed without
arguments, it will print the flag's value (if any) and exit successfully, printing anything).
otherwise, it will fail (exit status 1). The value may be of the form
`--flag=value`, `--flag value`, `-f value`, or `-f=value`. Single-letter flags
will match when grouped with other single letter flags, but will not have a
value, i.e. `arg -b -abc foo` will exit successfully without printing anything.
When using `arg` in a shell script, it is best to use quotes around `$@`, as in When using `arg` in a shell script, it is best to use quotes around `$@`, as in
`arg --foo "$@"`, so arguments with spaces will parse properly, like `arg --foo "$@"`, so arguments with spaces will be forwarded correctly.
`my_script.sh --flag "one two"`.

40
arg.c
View File

@ -12,39 +12,43 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#define EXIT_SUCCESS 0
#define EXIT_NO_MATCH 1
#define EXIT_BAD_USAGE -1
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
char *flag = argv[1]; char *flag = argv[1];
if (!flag || flag[0] != '-') { if (!flag) {
fprintf(stderr, "Usage: arg [-<f> | --<flag>] ...\n"); fprintf(stderr, "Usage: arg <flag> ...\n");
return 1; return EXIT_BAD_USAGE;
} }
size_t n = strlen(flag); size_t flaglen = strlen(flag);
// Look for: --flag=val, --flag val, --flag, -f=val, -f val, or -f // Look for: --flag=val, --flag val, --flag, -f=val, -f val, or -f
for (int i = 2; i < argc; i++) { for (int i = 2; i < argc; i++) {
char *arg = argv[i]; char *arg = argv[i];
if (strncmp(arg, flag, n) != 0) continue; if (strncmp(arg, flag, flaglen) != 0) continue;
if (arg[n] == '\0') { // --flag if (arg[flaglen] == '\0') { // --flag ...
if (argv[i+1] && argv[i+1][0] != '-') if (argv[i+1] && argv[i+1][0] != '-') // --flag <value>
puts(argv[i+1]); // value of flag, if any puts(argv[i+1]); // value of the flag
return 0; return EXIT_SUCCESS;
} else if (arg[n] == '=') { // --flag=... } else if (arg[flaglen] == '=') { // --flag=...
puts(&arg[n+1]); puts(&arg[flaglen+1]);
return 0; return EXIT_SUCCESS;
} }
} }
// If flag is single-character, e.g. -f, look for it among other single // If flag is single-character without a value, e.g. -f, look for it
// character flags like -xfy, in which case the next argument will not // among other single character flags like -xfy.
// be considered its value. if (flag[0] == '-' && flaglen == 2) {
if (n == 2) {
for (int i = 2; i < argc; i++) { for (int i = 2; i < argc; i++) {
char *arg = argv[i]; char *arg = argv[i];
if (arg[0] != '-' || arg[1] == '-') if (arg[0] != '-' || arg[1] == '-')
continue; // skip foo and --foo continue; // skip foo and --foo
for (char *c = &arg[1]; *c; c++) { for (char *c = &arg[1]; *c; c++) {
if (*c == flag[1]) return 0; if (*c == flag[1])
return EXIT_SUCCESS;
} }
} }
} }
return 1; return EXIT_NO_MATCH;
} }