Wednesday, 6 December 2017

How do I parse command line arguments in Bash?

itemprop="text">

Say, I have a script that gets called
with this line:



./myscript -vfd
./foo/bar/someFile -o
/fizz/someOtherFile


or
this one:




./myscript -v
-f -d -o /fizz/someOtherFile ./foo/bar/someFile



What's the accepted
way of parsing this such that in each case (or some combination of the two)
$v, $f, and
$d will all be set to true and
$outFile will be equal to
/fizz/someOtherFile ?


class="post-text" itemprop="text">
class="normal">Answer




Update: It's
been more than 5 years since I started this answer. Thank you for LOTS of great
edits/comments/suggestions. In order save maintenance time, I've modified the code block
to be 100% copy-paste ready. Please do not post comments like "What if you changed X to
Y…". Instead, copy-paste the code block, see the output, make the change, rerun the
script, and comment "I changed X to Y and…" I don't have time to test your ideas and
tell you if they work.



/>




Two common ways to
pass key-value-pair arguments are:



Bash
Space-Separated (e.g., --option argument) (without
getopt[s])



Usage
demo-space-separated.sh -e conf -s /etc -l /usr/lib
/etc/hosts






class="lang-bash prettyprint-override">cat
>/tmp/demo-space-separated.sh
<<'EOF'
#!/bin/bash

POSITIONAL=()
while
[[ $# -gt 0 ]]
do
key="$1"

case $key
in
-e|--extension)

EXTENSION="$2"
shift #
past argument
shift # past value
;;

-s|--searchpath)
SEARCHPATH="$2"
shift # past argument

shift # past value
;;
-l|--lib)


LIBPATH="$2"
shift # past argument
shift # past value

;;
--default)
DEFAULT=YES
shift # past
argument
;;
*) # unknown option
POSITIONAL+=("$1") #
save it in an array for later

shift # past argument

;;
esac
done
set -- "${POSITIONAL[@]}" # restore
positional parameters

echo "FILE EXTENSION =
${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH
= ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"

echo "Number
files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc
-l)
if [[ -n $1 ]]; then
echo "Last line of file specified as
non-opt/last argument:"
tail -1
"$1"
fi
EOF

chmod +x
/tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf
-s /etc -l /usr/lib
/etc/hosts



output
from copy-pasting the block above:



class="lang-none prettyprint-override">FILE EXTENSION =
conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT
=
Number files in SEARCH PATH with EXTENSION: 14
Last line of file
specified as non-opt/last argument:

#93.184.216.34
example.com


Bash
Equals-Separated (e.g., --option=argument) (without
getopt[s])



Usage
demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib
/etc/hosts



cat >/tmp/demo-equals-separated.sh
<<'EOF'
#!/bin/bash


for i in
"$@"
do
case $i in
-e=*|--extension=*)

EXTENSION="${i#*=}"
shift # past argument=value
;;

-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
shift # past
argument=value

;;
-l=*|--lib=*)

LIBPATH="${i#*=}"
shift # past argument=value
;;

--default)
DEFAULT=YES
shift # past argument with no
value
;;
*)

# unknown option

;;
esac
done
echo "FILE EXTENSION =
${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH
= ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in
SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc
-l)
if [[ -n $1 ]]; then

echo "Last line of file
specified as non-opt/last argument:"
tail -1
$1
fi
EOF

chmod +x
/tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf
-s=/etc -l=/usr/lib
/etc/hosts



output
from copy-pasting the block above:



class="lang-none prettyprint-override">FILE EXTENSION =
conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT
=
Number files in SEARCH PATH with EXTENSION: 14
Last line of file
specified as non-opt/last argument:
#93.184.216.34
example.com



To
better understand ${i#*=} search for "Substring Removal" in
rel="noreferrer">this guide. It is functionally equivalent to
`sed 's/[^=]*=//' <<< "$i"` which calls a needless
subprocess or `echo "$i" | sed 's/[^=]*=//'` which calls
two needless subprocesses.





from: href="http://mywiki.wooledge.org/BashFAQ/035#getopts"
rel="noreferrer">http://mywiki.wooledge.org/BashFAQ/035#getopts



getopt(1)
limitations
(older, relatively-recent getopt
versions):





  • can't handle
    arguments that are empty strings

  • can't handle arguments
    with embedded
    whitespace



More recent
getopt versions don't have these
limitations.



Additionally, the POSIX shell (and
others) offer getopts which doesn't have these limitations.
I've included a simplistic getopts
example.



Usage demo-getopts.sh -vf
/etc/hosts foo bar




class="lang-bash prettyprint-override">cat >/tmp/demo-getopts.sh
<<'EOF'
#!/bin/sh

# A POSIX
variable
OPTIND=1 # Reset in case getopts has been used previously in the
shell.

# Initialize our own
variables:
output_file=""
verbose=0


while
getopts "h?vf:" opt; do
case "$opt" in
h|\?)

show_help
exit 0
;;
v) verbose=1

;;
f) output_file=$OPTARG
;;


esac
done

shift $((OPTIND-1))

[
"${1:-}" = "--" ] && shift

echo "verbose=$verbose,
output_file='$output_file', Leftovers:
$@"
EOF


chmod +x
/tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo
bar


output from
copy-pasting the block above:



verbose=1, output_file='/etc/hosts', Leftovers: foo
bar



The
advantages of getopts
are:




  1. It's more portable,
    and will work in other shells like dash.

  2. It can handle multiple single options like
    -vf filename in the typical Unix way,
    automatically.



The
disadvantage of getopts is that it can only handle short
options (-h, not --help) without
additional code.



There is a href="http://wiki.bash-hackers.org/howto/getopts_tutorial" rel="noreferrer">getopts
tutorial which explains what all of the syntax and variables mean. In bash,
there is also help getopts, which might be
informative.



No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print ...