need help parsing shell script command line arguments - linux

I am new to Unix shell scripting and would like some help with writing small script.
I have defined a following synopsis for my script:
install.sh [-h|-a path|[-k path][-f path][-d path][-e path]]
ie user can request some help (-h), install all to a specified place (-a path), or install one or more of a components (-k, -f, -d -e) to a appropriate paths. If there is no arguments, the help should be shown.
Thanks in advance.

You can use getopts to parse a command line with bash. Here is an example taken from Bash/Parsing command line arguments using getopts (obviously you'd have to adjust the options to your needs).
#!/bin/bash
#Set a default value for the $cell variable
cell="test"
#Check to see if at least one argument was specified
if [ $# -lt 1 ] ; then
echo "You must specify at least 1 argument."
exit 1
fi
#Process the arguments
while getopts c:hin: opt
do
case "$opt" in
c) cell=$OPTARG;;
h) usage;;
i) info="yes"
n) name=$OPTARG;;
\?) usage;;
esac
done
Related SO question How do I parse command line arguments in bash?
For more information search for getopts on this man page for bash.

Related

How to pass an argument in a .shell script file

This is the first time i am trying to edit a .sh file on ubuntu
ng build #xyz--abc/my-${library}
I am trying to pass a flag --build-optimizer=false but if i add it to end it is giving error Unknown option: '--build-optimizer'
Need some help on how can I add this flag.
Thanks
Usually commandline argument option parsing is implemented with either the bash builtin getopts or one of the platform specific getopt implementations. While getopts does not support parsing long commandline options the GNU getopt implementation does. This answer points out in detail the differences between the corresponding implementations. For an example on how to use the bash builtin getopts I'll refer to the Advanced Bash Scripting Guide.
In order to give you a correct answer it therefore depends on how the script you're trying to modify implements commandline option parsing.
LS_OPTIONS=''
while [ $# -gt 0 ]; do
case "$1" in
--color=*)
colorarg="${1#*=}"
LS_OPTIONS="${LS_OPTIONS} ${1}"
;;
-a|--all)
allarg="${1}"
LS_OPTIONS="${LS_OPTIONS} ${1}"
;;
*)
printf "* Error: Invalid argument.*\n"
exit 1
esac
shift
done
printf "all val: %s\n\n" "$allarg"
printf "color val: %s\n\n" "$colorarg"
ls ${LS_OPTIONS}
Parses the arguments and passes them to the underlying ls. Is that what you are trying to achieve?

How to read the first line user types into terminal in bash script

I'm trying to write a script where to run the script, the user will type something along the lines of
$./cpc -c test1.txt backup
into the terminal, where ./cpc is to run the script, -c is $option, test1.txt is $source and backup is $destination.
How would I assign the values typed in to the terminal to use them in my script, for example in
if [[ -z $option || -z $source || -z $destination ]]; then
echo "Error: Incorrect number of arguments." (etc)
as when checking the script online the following errors return: 'option/source/destination is referenced but not assigned.'
Sorry in advance if any of this doesn't make sense, I'm trying to be as clear as possible
The arguments are stored in the numbered parameters $1, $2, etc. So, just assign them
option=$1
source=$2
destination=$3
See also man getopt or getopts in man bash.

getopts: How to accept arguments that aren't tied to an option in my script? [duplicate]

This question already has answers here:
How do I parse command line arguments in Bash?
(40 answers)
Closed 6 years ago.
I'm writing a Bash script that has the following usage:
ci-badge -t|-a [-b branch] [-m] [-d description] [-l [link] ] repo
Examples:
$ ci-badge -t foo/bar
$ ci-badge -ab dev foo/bar -m
$ ci-badge quux/bar -md 'Hello, world.'
More samples can be found here on GitHub. Anyway, I'm wondering exactly how to implement argument parsing for this script using getopts. After a few hours looking at this basic getopts guide and scouring SO, this is what my code looks so far:
#!/usr/bin/env bash
generate_url=false # set to true if -l is passed with no arg
markdown=false # true -> surround the URL with markdown
# Parse options
while getopts ':tab:md:l:' opt
do
case "$opt" in
a) service=appveyor ;;
b) branch=$OPTARG ;;
d) description=$OPTARG ;;
l) url=$OPTARG ;;
m) markdown=true ;;
t) service=travis ;;
# Colon: runs if no args are passed to
# an option that normally requires parameters.
:) test "$OPTARG" = l && generate_url=true ;;
# ?: Runs if an invalid option is passed.
'?') die ;;
esac
done
As you can see, most of the functionality is there, but I'm wondering how to accept repo as an argument to the script. Since getopts stops parsing after it encounters the first non-option argument, I'm wondering, how would I implement this (preferably with minimal complexity)? The guide I linked earlier doesn't seem to mention dealing with arguments that aren't associated with an option, so I'm a bit lost.
Thanks for helping!
Use $OPTIND value. After getopts cycle:
shift $((OPTIND-1))
echo $#
echo $1 $2 ...

I keep getting a 'while syntax' error on the output of the at job in unix and I have no idea why

#!/usr/dt/bin/dtksh
while getopts w:m: option
do
case $option in
w) wflag=1
wval="$OPTARG";;
m) mflag=1
mval="$OPTARG";;
?) printf 'BAD\n' $0
exit 2;;
esac
done
if [ ! -z "$wflag" ]; then
printf "W and -w arg is $wval\n"
fi
if [ ! -z "$mflag" ]; then
printf "M and -m arg is $mval\n"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: $* \n"
at $wval <<ENDMARKER
echo $* >> Search_List
tr " " "\n" <Search_List >Usr_List
while true; do
if [ -s Usr_List ]; then
for i in $(cat Usr_List); do
if finger -m | grep $i; then
echo '$i is online' | elm user
sed '/$i/d' <Usr_List >tmplist
mv tmplist Usr_List
fi
done
else
break
fi
done
ENDMARKER
Essentially I want to keep searching through until it is empty. Each time an element of the list is found, it is deleted. Once the list is empty quit.
There are no error messages when I first run the command, it only shows up in an email containing the output of the at job.
Thanks in advance for any advice
EDIT: The script uses getopts and takes one argument for -w and one for -m, the w value is set as the time for the at job, the m still has to be used. Any arguments after the one for m are sent to a file called Search_List, Search_List is edited and saved as Usr_List. Then in the while loop, while Usr_List is not empty, the script checks the results of finger -m against the names in Usr_List. If a name is found, it is removed from Usr_List. Once Usr_List is empty, the program should stop.
elm is a way to send an email, so elm user sends an email to user.
The error is :
while: Expression syntax
at uses /bin/sh by default.
at now <<ENDMARKER
<code here>
ENDMARKER
All of this executes under /bin/sh, which on some systems can be Bourne Shell (Solaris for example).
You need to figure out what /bin/sh is for your system, then modify things accordingly. Plus, read the gurantees about what is and what is not in your "at" environment. I think the problem lies there. You have both UNIX and linux tags. So I cannot give a lot more help than that.
You can enable logging -- the way YOU need it -- of the at code chunk:
exec 2&>1 > /tmp/somefile.log
Then write debugging messages to stdout or stderr.
Your HEREDOC is being interpolated. Try quoting the delimiter:
at $wval << 'ENDMARKER'
Although ( I haven't looked closely) it appears that you want some interpolation. But you definitely do not want it on the line in which you reference $i, so quote that $ if you do not quote the entire heredoc:
if finger -m | grep \$i; then
You need to pass the -k option to at:
...
at -k $wval <<ENDMARKER
...
at is otherwise defaulting to your login shell which is csh or one of its derivatives.
It turns out that the while command and the if command needed to be combined.
while [[ -s Usr_List ]]; do
......
done

Prevent ssh from breaking up shell script parameters

I have a script, which is essentially a wrapper around an executable by the same name on a different machine. For the sake of example, i'll wrap printf here. My current script looks like this:
#!/bin/bash
ssh user#hostname.tld. printf "$#"
Unfortunately, this breaks when one of the arguments contains a space, e.g. i'd expect the following commands to give identical outputs.:
~$ ./wrap_printf "%s_%s" "hello world" "1"
hello_world1_
~$ printf "%s_%s" "hello world" "1"
hello world_1
The problem gets even worse when (escaped) newlines are involved. How would I properly escape my arguments here?
Based on the answer from Peter Lyons, but also allow quotes inside arguments:
#!/bin/bash
QUOTE_ARGS=''
for ARG in "$#"
do
ARG=$(printf "%q" "$ARG")
QUOTE_ARGS="${QUOTE_ARGS} $ARG"
done
ssh user#hostname.tld. "printf ${QUOTE_ARGS}"
This works for everything i've tested so far, except newlines:
$ /tmp/wrap_printf "[-%s-]" "hello'\$t\""
[-hello'$t"-]
#!/bin/sh
QUOTE_ARGS=''
for ARG in "$#"
do
QUOTE_ARGS="${QUOTE_ARGS} '${ARG}'"
done
ssh user#hostname.tld. "${QUOTE_ARGS}"
This works for spaces. It doesn't work if the argument has an embedded single quote.
Getting quoting right is pretty hard and doing it in bash (in a general and robust way) almost impossible.
Use Perl:
#!/usr/bin/perl
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new('user#hostname');
$ssh->system('printf', #ARGV);
Based on the answers from Koert and Peter Lyons, here a wrapper for ssh; i call it "sshsystem". (also available at https://gist.github.com/4672115)
#!/bin/bash
# quote command in ssh call to prevent remote side from expanding any arguments
# uses bash printf %q for quoting - no idea how compatible this is with other shells.
# http://stackoverflow.com/questions/6592376/prevent-ssh-from-breaking-up-shell-script-parameters
sshargs=()
while (( $# > 0 )); do
case "$1" in
-[1246AaCfgKkMNnqsTtVvXxYy])
# simple argument
sshargs+=("$1")
shift
;;
-[bcDeFIiLlmOopRSWw])
# argument with parameter
sshargs+=("$1")
shift
if (( $# == 0 )); then
echo "missing second part of long argument" >&2
exit 99
fi
sshargs+=("$1")
shift
;;
-[bcDeFIiLlmOopRSWw]*)
# argument with parameter appended without space
sshargs+=("$1")
shift
;;
--)
# end of arguments
sshargs+=("$1")
shift
break
;;
-*)
echo "unrecognized argument: '$1'" >&2
exit 99
;;
*)
# end of arguments
break
;;
esac
done
# user#host
sshargs+=("$1")
shift
# command - quote
if (( $# > 0 )); then
# no need to make COMMAND an array - ssh will merge it anyway
COMMAND=
while (( $# > 0 )); do
arg=$(printf "%q" "$1")
COMMAND="${COMMAND} ${arg}"
shift
done
sshargs+=("${COMMAND}")
fi
exec ssh "${sshargs[#]}"
The easiest and quickest is to just use Bash's Quoting Parameter Transformation: ${parameter#Q}. This can automatically applied during array expansion with ${array[#]#Q}, but when using the builtin argument array, the name and the brackets are dropped, so it becomes ${##Q}. Therefore the original script only needs 4 characters added to it to work.
#!/bin/bash
ssh user#hostname.tld. printf "${##Q}"
Now any escaping will work, even terminal colors like this:
./wrap_printf "%s\e[39m\e[49m\n" $'\e[30m\e[42mBlack on Green' "Just Normal Text"

Resources