Skip to content

Instantly share code, notes, and snippets.

@apainintheneck
Last active December 25, 2023 00:40
Show Gist options
  • Save apainintheneck/2bd9bde17c7700eb429945ab80c70fa8 to your computer and use it in GitHub Desktop.
Save apainintheneck/2bd9bde17c7700eb429945ab80c70fa8 to your computer and use it in GitHub Desktop.
A blatant clone of the `choose` command written in awk.
#!/usr/bin/env sh
awk '
BEGIN {
TRUE = 1
FALSE = 0
arg_parse()
if(HELP) {
print_help()
exit(0)
}
}
{
choice_list = ""
for(idx = 1; idx <= NF; ++idx) {
if(valid_choice(idx)) {
if(choice_list)
choice_list = choice_list OFS $idx
else
choice_list = $idx
}
}
if(choice_list) print choice_list
}
function arg_parse( idx, unused_args) {
idx = 2 # Skip the first arg since it will always be `--`.
# Parse options
for(; idx < ARGC; ++idx) {
if(ARGV[idx] == "-h" || ARGV[idx] == "--help") {
HELP = TRUE
} else if(ARGV[idx] == "-f" || ARGV[idx] == "--field-separator") {
if(CHARACTER_WISE) {
print "Error: The -c and -f options cannot be used together"
exit(1)
}
FS = ARGV[++idx]
puts FS
} else if(ARGV[idx] == "-i" || ARGV[idx] == "--input") {
INPUT_FILES[ARGV[++idx]] = TRUE
} else if(ARGV[idx] == "-o" || ARGV[idx] == "--output-field-separator") {
OFS = ARGV[++idx]
} else {
break
}
if(ARGC == idx) {
print "Error: Missing option parameter for: " ARGV[idx - 1]
exit(1)
}
}
# Parse choices
for(; idx < ARGC; ++idx) {
if(!parse_choice(ARGV[idx])) break
}
# Check for unused arguments
if(idx < ARGC) {
unused_args = ARGV[idx]
for(idx++; idx < ARGC; idx++)
unused_args = unused_args " " ARGV[idx]
print "Error: Unused arguments: " unused_args
exit(1)
}
# Add input files to ARGV
ARGC = 1
for(file_name in INPUT_FILES)
ARGV[ARGC++] = file_name
}
function parse_choice(raw_choice, choice_start, choice_end, idx) {
if(raw_choice ~ /^(-)?[0-9]+$/) {
if(raw_choice >= 0)
POSITIVE_CHOICES[raw_choice + 1] = TRUE
else
NEGATIVE_CHOICES[raw_choice + 1] = TRUE
} else if(raw_choice ~ /^:(-)?[0-9]+$/) {
choice_end = substr(raw_choice, 2)
if(choice_end >= 0) {
choice_end++
for(idx = 1; idx < choice_end; ++idx)
POSITIVE_CHOICES[idx] = TRUE
} else if(!MAX_CHOICE || MAX_NEGATIVE_CHOICE < choice_end) {
MAX_CHOICE = TRUE
MAX_NEGATIVE_CHOICE = choice_end + 0
}
} else if(raw_choice ~ /^(-)?[0-9]+:$/) {
choice_start = substr(raw_choice, 1, length(raw_choice) - 1)
if(choice_start < 0) {
for(; 0 > choice_start; ++choice_start) {
NEGATIVE_CHOICES[choice_start + 1] = TRUE
}
} else if(!MIN_CHOICE || MIN_POSITIVE_CHOICE > choice_start + 1) {
MIN_CHOICE = TRUE
MIN_POSITIVE_CHOICE = choice_start + 1
}
} else if(raw_choice ~ /^(-)?[0-9]+:(-)?[0-9]+$/) {
choice_start = substr(raw_choice, 1, index(raw_choice, ":") - 1) + 0
choice_end = substr(raw_choice, index(raw_choice, ":") + 1) + 0
if((choice_start < 0 && choice_end >= 0) || (choice_start >= 0 && choice_end < 0)) {
print "Error: Invalid choice: Ranges must be entirely positive or entirely negative: " raw_choice
exit(1)
}
for(; choice_start <= choice_end; ++choice_start) {
if(choice_start >= 0)
POSITIVE_CHOICES[choice_start + 1] = TRUE
else
NEGATIVE_CHOICES[choice_start + 1] = TRUE
}
} else {
return FALSE
}
return TRUE
}
function valid_choice(idx) {
if(POSITIVE_CHOICES[idx])
return TRUE
else if(NEGATIVE_CHOICES[idx - NF])
return TRUE
else if(MIN_CHOICE && idx >= MIN_POSITIVE_CHOICE)
return TRUE
else if(MAX_CHOICE && NF + MAX_NEGATIVE_CHOICE + 1 >= idx)
return TRUE
else
return FALSE
}
function print_help() {
print "tyranny"
print ""
print "A blatant `choose` clone. (See https://github.com/theryangeary/choose)"
print ""
print "USAGE:"
print " tyranny [OPTIONS] <choices>..."
print ""
print "OPTIONS:"
print " -f, --field-separator <field-separator>"
print " Specify field separator other than whitespace, using Awk `regex` syntax"
print " -i, --input <input> Input file"
print " -o, --output-field-separator <output-field-separator> Specify output field separator"
print " -h, --help Prints help information"
print ""
print "ARGS:"
print " <choices>... Fields to print. Either a or a:b where a and b are integers. The beginning or end"
print " of a range can be omitted, resulting in including the beginning or end of the line,"
print " respectively. a:b is inclusive of b and they must be positive or both must be negative"
}' -- $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment