Pass commandline args into another script - bash

I have couple of scripts which call into each other. However when I pass
Snippet from buid-and-run-node.sh
OPTIND=1 # Reset getopts in case it was changed in a previous run
while getopts "hn:c:f:s:" opt; do
case "$opt" in
h)
usage
exit 1
;;
n)
container_name=$OPTARG
;;
c)
test_command=$OPTARG
;;
s)
src=$OPTARG
;;
*)
usage
exit 1
;;
esac
done
$DIR/build-and-run.sh -n $container_name -c $test_command -s $src -f $DIR/../dockerfiles/dockerfile_node
Snippet from build-and-run.sh
OPTIND=1 # Reset getopts in case it was changed in a previous run
while getopts "hn:c:f:s:" opt; do
case "$opt" in
h)
usage
exit 1
;;
n)
container_name=$OPTARG
;;
c)
test_command=$OPTARG
;;
f)
dockerfile=$OPTARG
;;
s)
src=$OPTARG
;;
*)
usage
exit 1
;;
esac
done
I am calling it as such
build-and-run-node.sh -n test-page-helper -s ./ -c 'scripts/npm-publish.sh -r test/test-helpers.git -b patch'
with the intention that npm-publish.sh should run with the -r and -b parameters. However when I run the script I get
build-and-run.sh: illegal option -- r
which obviously means it is the build-and-run command that is consuming the -r. How do I avoid this?

You need double quotes around $test_command in buid-and-run-node.sh, otherwise that variable is being split on the white space and appears to contain arguments for buid-and-run.sh. Like this:
$DIR/build-and-run.sh -n $container_name -c "$test_command" -s $src -f $DIR/../dockerfiles/dockerfile_node
Further Info
As the comment below rightly points out, it's good practice to quote all variables in Bash, unless you know you want them off (for example, to enable shell globbing). It's also helpful, at least in cases where the variable name is part of a larger word, to use curly braces to delineate the variable name. This is to prevent later characters from being treated as part of the variable name if they're legal. So a better command call might look like:
"${DIR}/build-and-run.sh" -n "$container_name" -c "$test_command" -s "$src" -f "${DIR}/../dockerfiles/dockerfile_node"

Related

How to enable autocomplete subcommnads on function that's call kubectl

Let's imagine you want to save your time writing all kubectl command: kubectl describe pods in shorter way: k d p.
So the solution is to add function to ~/.bashrc:
k() {
cmd_kubectl="command kubectl"
case ${1} in
g)
shift
kubectl_get="${cmd_kubectl} get"
case ${1} in
p)
shift
${kubectl_get} pods "$#"
;;
d)
shift
${kubectl_get} deploy "$#"
;;
n)
shift
${kubectl_get} ns "$#"
;;
i)
shift
${kubectl_get} ing "$#"
;;
j)
shift
${kubectl_get} job "$#"
;;
*)
${kubectl_get} "$#"
;;
esac
;;
d)
shift
kubectl_desc="${cmd_kubectl} describe"
case ${1} in
p)
shift
${kubectl_desc} pods "$#"
;;
d)
shift
${kubectl_desc} deploy "$#"
;;
n)
shift
${kubectl_desc} ns "$#"
;;
i)
shift
${kubectl_desc} ing "$#"
;;
j)
shift
${kubectl_desc} job "$#"
;;
*)
${kubectl_desc} "$#"
;;
esac
;;
*)
${cmd_kubectl} "$#"
;;
esac
}
But I'd like to save an effort and improve it more, so my question is:
How to enable auto-completion on this function to allow fill e.g. the name of the pod?
Usage:
k d p -> Tab -> k d p nginx (result)
I tried to think up thanks following references, but probably I'm not enough experienced Linux user/developer to compose the final solution.
How to enable kubernetes commands autocomplete
How to enable bash auto-completion for a function?
https://askubuntu.com/questions/68175/how-to-create-script-with-auto-complete
Thanks in advance!
I also wanted to make it easier to write kubectl commands, but I solved it in a slightly different way.
I will describe this method below as I think you may find it useful.
I keep all the necessary files in the ~/.bash_completion.d directory but you can modify it depending on your needs.
$ mkdir ~/.bash_completion.d
First, I enabled kubectl autocompletion as described in the Kubernetes documentation:
kubectl completion bash > ~/.bash_completion.d/kubectl
Then I downloaded complete-alias - automagical shell alias completion:
NOTE: More information on complete-alias can be found here.
$ curl https://raw.githubusercontent.com/cykerway/complete-alias/master/complete_alias > ~/.bash_completion.d/complete_alias
Next I created the kubectl_aliases file with the aliases I want to use:
$ cat ~/.bash_completion.d/kubectl_aliases
alias kgp='kubectl get pods'
complete -F _complete_alias kgp
alias kgd='kubectl get deploy'
complete -F _complete_alias kgd
alias kgn='kubectl get ns'
complete -F _complete_alias kgn
alias kgi='kubectl get ing'
complete -F _complete_alias kgi
alias kgj='kubectl get job'
complete -F _complete_alias kgj
alias kg='kubectl get'
complete -F _complete_alias kg
Finally, we can execute commands from the files in the ~/.bash_completion.d directory and check if it works as expected:
$ source ~/.bash_completion.d/kubectl
$ source ~/.bash_completion.d/complete_alias
$ source ~/.bash_completion.d/kubectl_aliases
After typing kgp and pressing Tab twice, we got a choice of Pods:
$ kgp
app-1 nginx webserver
$ kgp app-1
NAME READY STATUS RESTARTS AGE
app-1 1/1 Running 0 5m28s

How to pass multiple flag and multiple arguments in getopts in shell script? [duplicate]

This question already has an answer here:
bash getopts with multiple and mandatory options
(1 answer)
Closed 1 year ago.
Answer given here doesn't here any of my questions: bash getopts with multiple and mandatory options
I am new to shell script and trying to write a shell script with getopts meanwhile I couldn't find answers to the below questions. Can somebody please help here?
This is the script I've started writing:
#!/bin/bash
while getopts c:t:x: option ; do
case $option in
c)
java -jar $file --fail-if-no-tests --include-classname "$OPTARG" --scan-class-path $file --include-tag regression
;;
t)
java -jar $file --fail-if-no-tests --include-classname '.*' --scan-class-path $file --include-tag "$OPTARG"
;;
c | t)
java -jar $file --fail-if-no-tests --include-classname "$OPTARG" --scan-class-path $file --include-tag "$OPTARG"
;;
*)
usage
esac
done
How do I pass multiple flags? For example runner.sh -c classname -t tagname I Tried using ct, c|t in the getopts but did not work.
How do I pass multiple arguments for one flag? For example runner.sh -g arg1 arg2
Is there a way if I don't provide any flag
it will run by default statement. I tried to achieve this using *
but there is one issue with this approach. In case the wrong flag is
provided it will always run default statement.
while getopts 'c:t:x:' option; do
case "$option" in
c ) classname="$OPTARG" ;;
t ) tagname="$OPTARG" ;;
x ) xvar="$OPTARG" ;;
esac
done
## now you can use the variables in your command

Control wildcard expansion in sh command

I have a program which executes shell functions using call like sh -c "<command_string>". I can not change the way of calling these functions.
In this program I call different self written helper shell function, which are sourced into my environment. One of these looks like this. It unzips files with a given file pattern into a given directory.
function dwhUnzipFiles() {
declare OPTIND=1
while getopts "P:F:T:" opt; do
case "$opt" in
P) declare FILEPATTERN="$OPTARG" ;;
F) declare FROMDIR="$OPTARG" ;;
T) declare TODIR="$OPTARG" ;;
*) echo "Unbekannte Option | Usage: dwhUnzipFiles -P <filepattern> -F <fromdir> -T <todir>"
esac
done
shift $((OPTIND-1))
for currentfile in "${FROMDIR}"/"${FILEPATTERN}" ; do
unzip -o "$currentfile" -d "${TODIR}";
done
# error handling
# some more stuff
return $?
}
For this function I use arguments with wildcards for the FILEPATTERN variable. The function gets called by my program like this:
sh -c ". ~/dwh_env.sh && dwhUnzipFiles -P ${DWH_FILEPATTERN_MJF_WLTO}.xml.zip -F ${DWH_DIR_SRC_XML_CURR} -T ${DWH_DIR_SRC_XML_CURR}/workDir" where ${DWH_FILEPATTERN_MJF_WLTO} contains wildcards.
This works as intended. My confusion starts with another helper function, which is constructed in a similar way, but I'm not able to control the wildcard expansion correctly. It just deletes files in a directory depending on a given file pattern.
function dwhDeleteFiles() {
declare retFlag=0
declare OPTIND=1
while getopts "D:P:" opt; do
case "$opt" in
D) declare DIRECTORY="$OPTARG" ;;
P) declare FILEPATTERN="$OPTARG" ;;
*) echo "Unbekannte Option | Usage: dwhDeleteFiles -D <Directory> -P <Filepattern>"
esac
done
shift $((OPTIND-1))
for currentfile in "${DIRECTORY}"/"${FILEPATTERN}" ; do
rm -fv "${currentfile}";
done
# error handling
# some more stuff
return $retFlag
}
This function is called like this:
sh -c ". ~/dwh_env.sh && dwhDeleteFiles -P ${DWH_FILEPATTERN_MJF_WLTO}.xml -D ${DWH_DIR_SRC_XML_CURR}/workDir" where again ${DWH_FILEPATTERN_MJF_WLTO} contains wildcards. When I call this function with my program it results in doing nothing. I tried to play around with adding "" and \"\" to the arguments of my functions, but all what is happening is that instead of deleting all files in the given directory the function deletes only the first one in an alphanumerical order.
Can somebody explain to me, what is happening here? My idea is that the multiple passing of the variable, containing the wildcard, is not working. But how do I fix this and is it even possible in bash? And why is the dwhUnzipFilesfunction working and the dwhDeleteFiles is not?
Suppose that DWH_FILEPATTERN_MJF_WLTO is *, and you have a bunch of *.xml files, then the command is
dwhDeleteFiles -P *.xml -D ${DWH_DIR_SRC_XML_CURR}/workDir
which expands to
dwhDeleteFiles -P bar.xml baz.xml foo.xml zap.xml -D ${DWH_DIR_SRC_XML_CURR}/workDir
(Note alphabetical order of xml files). But the -P option only takes one arg, bar.xml (the first) and the remaining are treated as file arguments.
Try setting set -x in your script to see this in action.

Getopt parsing error in bash script (option declaration mistake)

I need to get 4 options (each with a short and a long version) in a bash script.
Here is what I did:
OPTS=`getopt -l :author,icon,channel,message: -o :aicm: -- "$#"` ||
exit 1
eval set -- "$OPTS"
while true; do
case "$1" in
-a|--author) echo "A:'$2'"; shift;;
-i|--icon) echo "I:'$2'"; shift 2;;
-m|--message) echo "M:'$2'"; shift 2;;
-c|--channel) echo "C:'$2'"; shift 2;;
--) shift; break;;
*) echo Error; exit 1;;
esac
done
And here is what I get:
command
docker run --rm -e SLACK_TOKEN slacker notify --channel foo
output
C:'--'
Error
Of course, I would like to have this output:
C:'foo'
Your getopt command looks a little funky. You seem to be using : as some sort of delimiter, here:
-l :author,icon,channel,message:
And here:
-o :aicm:
That doesn't make any sense. The : has special meaning in the options definitions; take a look at the getopt(1) man page:
-l, --longoptions longopts
The long (multi-character) options to be recognized. More than one
option name may be specified at once, by separating the names
with commas. This option may be given more than once, the longopts
are cumulative. Each long option name in longopts may be followed
by one colon to indicate it has a required argument, and by two colons
to indicate it has an optional argument.
The same is true of short options.
So assuming that all of your options take arguments, you would write:
OPTS=`getopt -l author:,icon:,channel:,message: -o a:i:c:m: -- "$#"` ||

Mass arguments (operands) at first place in command line argument passing

I use following lines (hope this is best practice if not correct me please) to handle command line options:
#!/usr/bin/bash
read -r -d '' HELP <<EOF
OPTIONS:
-c Enable color output
-d Enable debug output
-v Enable verbose output
-n Only download files (mutually exclusive with -r)
-r Only remove files (mutually exclusive with -n)
-h Display this help
EOF
# DECLARE VARIABLES WITH DEFAULT VALUES
color=0
debug=0
verbose=0
download=0
remove=0
OPTIND=1 # Reset in case getopts has been used previously in the shell
invalid_options=(); # Array for invalid options
while getopts ":cdvnrh" opt; do
echo "Actual opt: $opt"
case $opt in
c)
color=1
;;
d)
debug=1
;;
v)
verbose=1
;;
n)
download=1
;;
r)
remove=1
;;
h)
echo "$HELP"
exit 1
;;
\?)
invalid_options+=($OPTARG)
;;
*)
invalid_options+=($OPTARG)
;;
esac
done
# HANDLE INVALID OPTIONS
if [ ${#invalid_options[#]} -ne 0 ]; then
echo "Invalid option(s):" >&2
for i in "${invalid_options[#]}"; do
echo $i >&2
done
echo "" >&2
echo "$HELP" >&2
exit 1
fi
# SET $1 TO FIRST MASS ARGUMENT, $2 TO SECOND MASS ARGUMENT ETC
shift $((OPTIND - 1))
# HANDLE CORRECT NUMBER OF MASS OPTIONS
if [ $# -ne 2 ]; then
echo "Correct number of mass arguments are 2"
echo "" >&2
echo "$HELP" >&2
exit 1
fi
# HANDLE MUTUALLY EXCLUSIVE OPTIONS
if [ $download -eq 1 ] && [ $remove -eq 1 ]; then
echo "Options for download and remove are mutually exclusive" >&2
echo "$HELP" >&2
exit 1
fi
echo "color: $color"
echo "debug: $debug"
echo "verbose: $verbose"
echo "download: $download"
echo "remove: $remove"
echo "\$1: $1"
echo "\$2: $2"
If I call the script way that mass arguments (those that are not switches or arguments for switches) are last arguments everything is working correctly:
$ ./getopts.sh -c -d -v -r a b
Actual opt: c
Actual opt: d
Actual opt: v
Actual opt: r
color: 1
debug: 1
verbose: 1
download: 0
remove: 1
$1: a
$2: b
The problem is when I want to call the script so the mass arguments are first (or somewhere in the middle of switches that do not use arguments)
$ ./getopts.sh a b -c -d -v -r
Correct number of mass arguments are 2
OPTIONS:
-c Enable color output
-d Enable debug output
-v Enable verbose output
-n Only download files (mutually exclusive with -r)
-r Only remove files (mutually exclusive with -d)
-h Display this help
or
$ ./getopts.sh -c a b -d -v -r
Actual opt: c
Correct number of mass arguments are 2
OPTIONS:
-c Enable color output
-d Enable debug output
-v Enable verbose output
-n Only download files (mutually exclusive with -r)
-r Only remove files (mutually exclusive with -d)
-h Display this help
I think this should be OK according (POSIX) standards, because following syntax which is basically the same is working as expected on my system:
$ cp test1/ test2/ -r
$ cp test1/ -r test2/
I have search over the Internet but only thing that was close to my problem was this one related to C.
getopts automatically breaks the while loop as soon as it detects a non-dash parameter (not including the argument given to dash parameters that take arguments). The POSIX standard is to have dashed parameters come first, and then have files. There's also none of this -- and + crap either. It's plain and simple.
However, Linux isn't Unix or POSIX compliant. It's just in the nature of the GNU utilities to be "better" than the standard Unix utilities. More features, more options, and handling things a bit differently.
On Linux, command line parameters can come after files in many GNU utilities.
For example:
$ cp -R foo bar
Work on my Unix certified Mac OS X and on Linux, However,
$ cp foo bar -R
Only works on Linux.
If you want getopts to work like a lot of Linux utilities, you need to do a wee bit of work.
First, you have to process your arguments yourself, and not depend upon $OPTIND to parse them. You also need to verify that you have an argument.
I came up with this as an example of doing what you want.
#! /bin/bash
while [[ $* ]]
do
OPTIND=1
echo $1
if [[ $1 =~ ^- ]]
then
getopts :a:b:cd parameter
case $parameter in
a) echo "a"
echo "the value is $OPTARG"
shift
;;
b) echo "b"
echo "the value is $OPTARG"
shift
;;
c) echo "c"
;;
d) echo "d"
;;
*) echo "This is an invalid argument: $parameter"
;;
esac
else
other_arguments="$other_arguments $1"
fi
shift
done
echo "$other_arguments"
I now loop as long as $* is set. (Maybe I should use $#?) I have to do a shift at the end of the loop. I also reset $OPTIND to 1 each time because I'm shifting the arguments off myself. $OPTARG is still set, but I have to do another shift to make sure everything works.
I also have to verify if a argument begins with a dash or not using a regular expression in my if statement.
Basic testing shows it works, but I can't say it's error free, but it does give you an idea how you have to handle your program.
There's still plenty of power you're getting from getopts, but it does take a bit more work.
Bash provides two methods for argument parsing.
The built-in command getopts is a newer, easy to use mechanism how to parse arguments but it is not very flexible. getopts does not allow to mix options and mass arguments.
The external command getopt is an older and more complex mechanism to parse arguments. It allows long/short options and the gnu extension allow to mix options and mass arguments.

Resources