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
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
complicated. It's much nicer to be able to write shell scripts like this:
command line argument. Tools like `getopt` and `getopts` are unnecessarily
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
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
echo "Downloading to $dir"
fi
cd $path && curl -O example.com/file.zip
curl "$server/file.zip" > "$dir/file.zip"
```
## Usage
The following forms are accepted: `arg -f ...`, `arg -flag ...`, or `arg --flag
...`. If `arg` finds the first given flag among the rest of the command line
arguments, it will print the flag's value (if any) and exit successfully,
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.
The flag's value may be of the form `<flag>=<value>` or `<flag> <value>`.
Single-letter flags will also match when grouped with other single letter
flags, but will not have a value (e.g. `arg -b -abc foo` will succeed without
printing anything).
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
`my_script.sh --flag "one two"`.
`arg --foo "$@"`, so arguments with spaces will be forwarded correctly.

40
arg.c
View File

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