this post was submitted on 22 Jul 2025
22 points (100.0% liked)

Shell Scripting

1509 readers
1 users here now

From Ash, Bash and Csh to Xonsh, Ysh and Zsh; all shell languages are welcome here!

Rules:
  1. Follow Lemmy rules!
  2. Posts must relate to shell scripting. (See bottom of sidebar for more information.)
  3. Only make helpful replies to questions. This is not the place for low effort joke answers.
  4. No discussion about piracy or hacking.
  5. If you find a solution to your problem by other means, please take your time to write down the steps you used to solve your problem in the original post. You can potentially help others having the same problem!
  6. These rules will change as the community grows.

Keep posts about shell scripting! Here are some guidelines to help:


In general, if your submission text is primarily shell code, then it is welcome here!

founded 2 years ago
MODERATORS
 

This is a little tutorial that I found in my search to learn how to use getopt (mind: not getopts, which is a completely different thing). I want to share it here because I find it refreshingly to the point. Just the main code block already tells almost the whole story:

#!/bin/bash
# Set some default values:
ALPHA=unset
BETA=unset
CHARLIE=unset
DELTA=unset

usage()
{
  echo "Usage: alphabet [ -a | --alpha ] [ -b | --beta ]
                        [ -c | --charlie CHARLIE ] 
                        [ -d | --delta   DELTA   ] filename(s)"
  exit 2
}

PARSED_ARGUMENTS=$(getopt -a -n alphabet -o abc:d: --long alpha,bravo,charlie:,delta: -- "$@")
VALID_ARGUMENTS=$?
if [ "$VALID_ARGUMENTS" != "0" ]; then
  usage
fi

echo "PARSED_ARGUMENTS is $PARSED_ARGUMENTS"
eval set -- "$PARSED_ARGUMENTS"
while :
do
  case "$1" in
    -a | --alpha)   ALPHA=1      ; shift   ;;
    -b | --beta)    BETA=1       ; shift   ;;
    -c | --charlie) CHARLIE="$2" ; shift 2 ;;
    -d | --delta)   DELTA="$2"   ; shift 2 ;;
    # -- means the end of the arguments; drop this, and break out of the while loop
    --) shift; break ;;
    # If invalid options were passed, then getopt should have reported an error,
    # which we checked as VALID_ARGUMENTS when getopt was called...
    *) echo "Unexpected option: $1 - this should not happen."
       usage ;;
  esac
done

echo "ALPHA   : $ALPHA"
echo "BETA    : $BETA "
echo "CHARLIE : $CHARLIE"
echo "DELTA   : $DELTA"
echo "Parameters remaining are: $@"

Just be sure to correct the inadvertent mixing of beta and bravo.

top 6 comments
sorted by: hot top controversial new old
[–] FizzyOrange@programming.dev 5 points 1 week ago (1 children)

If you find yourself needing this, the correct thing to do is to stop writing that shell scripts and switch to a proper language.

[–] tjhowse@lemmy.world 9 points 1 week ago

While I agree with you, I think it's a bit unkind to reply in this way. Sometimes bash is all you have, and it's interesting to learn about tools even if you're unlikely to use them.

[–] SubArcticTundra@lemmy.ml 2 points 1 week ago* (last edited 1 week ago)

I still think my favorite shell, NuShell, handles this the best:

def alphabet [
    --alpha (-a)
    --charlie (-c): string    # comments on these lines are parsed and used for automatically generated --help flag
    --delta: int
    ...filenames: string
] { echo \$alpha }"

So much less boilerplate

[–] MonkderVierte@lemmy.zip 2 points 1 week ago* (last edited 1 week ago)

I've got one from stackoverflow i tidied up and expanded, that handles long options as well, but you really should learn Python instead. Talking from experience; even with a state machine approach and helpful little functions and debug helpers, juggling so much input is hard in shell.

Well, you're warned now and here it is:

tool_name="tool"
tool_descr="tool does tool things"

printhelp() {
	printf '%s\nSyntax: %s [options]...\n%s\n' "$tool_descr" "$tool_name" "$tool_args"
}
bool() { [ -n "$OPTARG" ] && error "--${OPT} accepts no parameters" 7; }
# cli-parser
#--------------------------------------------------------------
tool_args='
  -s, --stuff          stuff to do
  -o, --other          other stuff to do

  -V, --verbose        Tell what i do
  -v, --version        Print version of this tool and exit
  -h, --help           This help text
'
while getopts "vVhbs:o:-:" OPT; do
	# support long options: https://stackoverflow.com/a/28466267/519360
	if [ "$OPT" = "-" ]; then           # long option: reformulate OPT and OPTARG
		OPT="${OPTARG%%=*}"             # extract long option name
		OPTARG="${OPTARG#"$OPT"}"       # extract long option argument (may be empty)
		OPTARG="${OPTARG#=}"            # if long option argument, remove assigning `=`
	fi
	case "$OPT" in
		s | stuff )       arg_stuff="$OPTARG"
	;;  o | other )       arg_other="$OPTARG"
	;;  V | verbose )     bool; verbose=true
	;;  v | version )     bool; printversion; exit 0
	;;  h | help )        printhelp; exit 0
	;;  ??* )             error "Illegal option --$OPT" 22  # bad long option
	;;  : )               exit 2                            # missing parameter (error reported via getopts)
	;;  ? )               exit 22                           # bad short option (error reported via getopts)
	esac
done
[ $OPTIND -eq 1 ] && \
if { [ -e "$1" ] && [ $# -gt 0 ]; }                         # check for lazy file argument
	then lazy_arg="$1"
	else printhelp && exit 0
fi
shift $((OPTIND-1)) # remove parsed options and args from $@ list
[–] thatsit@sh.itjust.works 1 points 1 week ago

There is also the manual way, I am not saying it's perfect but sometimes it offers better alternative than getopt.
https://gist.github.com/dgoguerra/9206418

[–] sjohannes@programming.dev 1 points 1 week ago* (last edited 1 week ago)

The article mentions "GNU getopt" but as far as I know GNU doesn't have a getopt utility; the version on most Linux OSes comes from util-linux. (Perhaps the author is confusing it with the C function with the same name.)

Note that other getopt implementations have different features and some are simply broken. For example, BSD getopt doesn't support long options and comes with this known bug:

Arguments containing whitespace or embedded shell metacharacters generally will not survive intact; this looks easy to fix but isn't.

For cross-platform scripts it's probably best to use the getopts shell builtin instead, the downside being it only supports fairly basic (POSIX) syntax even on Bash.