Set specific option sets for arguments in bash - linux

I've a script where I can pass different set of arguments for the work the script has to do. The options are specified below:
[Show Help] ./myscript.sh -h
[To Create] ./myscript.sh -c -f /input_loc/input_file.txt
[To Export] ./myscript.sh -e -d /destination_loc/exported_db_date.csv
[To Import] ./myscript.sh -i -s /source_loc/to_import_date.csv
And the script myscript.sh has getopts to parse the options and then push it to case - esac to apply checks and logics for each of the arguments passed to check their validity. For example, like below:
while getopts "cd:ef:his:" o; do
case "${o}" in
Put options and their check logics
esac
done
My question is, what logic can I use to force arguments to be presented in particular sets only, for e.g.
Allowed Option set
./myscript.sh -h
./myscript.sh -c -f <file_name>
./myscript.sh -e -d <file_name>
./myscript.sh -i -s <file_name>
NOT Allowed Option set
./myscript.sh -h -c -f <file_name>
./myscript.sh -h -d <file_name>
./myscript.sh -e -s <file_name>
./myscript.sh -c -i -f <file_name>

I think your specific usage of getopts() here is needed only to parse the valid option flags first and put them into an array and use the parsed flags later for ensuring only if the required order of flags are set.
The steps involved are two-fold. Firstly parse the valid flags to an array (multi) and secondly join the flags together without spaces and do a regex match on the provided flags, if you encounter ones that aren't allowed don't do your logic.
#!/usr/bin/env bash
while getopts "cd:ef:his:" opt; do
case "$opt" in
c|d|e|f|h|i|s) multi+=("$opt");;
*) printf 'invalid flag provided' 1>&2 ; exit 1 ;;
esac
done
joined=$(IFS=;echo "${multi[*]}")
if ! [[ $joined =~ ^(hcf|hd|es|cif)$ ]]; then
printf 'valid flags provided\n'
# Add your logic here
fi
Note, the answer does consider the option of swapping your flags order, i.e. if ./myscript.sh -h -c -f <file_name> isn't allowed, should ./myscript.sh -f <file_name> -h -c be allowed? Need to modify the answer based on that.

I've used if...elif...else loop for this
#!/bin/bash
my_ary=("$#")
LEN=${#my_ary[#]}
if [[ ${my_ary[0]} =~ "-h" && $LEN -eq 1 ]] || \
[[ ${my_ary[0]} =~ "-c" && ${my_ary[1]} =~ "-f" && $LEN -eq 3 ]] || \
[[ ${my_ary[0]} =~ "-e" && ${my_ary[1]} =~ "-d" && $LEN -eq 3 ]] || \
[[ ${my_ary[0]} =~ "-i" && ${my_ary[1]} =~ "-s" && $LEN -eq 3 ]]; then
echo "Proper set of arguments passed"
else
echo "You are not passing arguments in proper sets, please look at Usage once again..."
fi
Is there a better way to do the same checks?

Related

bash script to add and remove users

I am a beginner in bash scripting and I have created a bash script to add users and remove users on Linux. But since I am facing some issues with the script not really major issues but would be helpful if anyone could point me how to improve the script and the worst practice I am doing the script would be helpful
however the problem I have noticed is that the script takes -a to add a user -d to remove user and -h to get help the -a flag as 2 optional arguments -p for password and -s for shell so the command would be
./useradd.sh -a user -p password -s shell
this works as expected and the user is added to the system but the problem I am facing is that if I do not enter -a flag and specify the -s and -p flag the script is just exited I want to show a clear idea to the user why it exited and there is so many such errors I am assuming but I have not tested it out so much any help would be appreciated, so here is my script
#!/bin/bash
## checking if the user is privileged or not
if [[ $EUID != 0 ]]
then
echo "Script has to be ran as root or sudo"
echo "Aborting"
exit 101
fi
## creating help functions
function usage() {
echo "usage: ${0} -a <user> -p <password> -s <shell> | ${0} -d <user> | ${0} -h"
}
function help() {
echo "$0 - Script to add of remove users"
echo "-a - Add a new user"
echo " -p - Set password while creating user if not mentioned will not set any password by default"
echo " -s - Set a shell for the user default is /bin/bash if none specified"
echo "-a - Remove a user"
echo "-h - Print this help text"
}
if [[ "$#" -lt "1" ]]; then
echo "Argument has to be provided see $0 -h"
fi
shell=/bin/bash
password=$(openssl rand -base64 32)
while getopts :a:d:h opt; do
case $opt in
a) user=$OPTARG
while getopts :p:s: test
do
case $test in
p) password=$OPTARG;;
s) shell=$OPTARG;;
/?) echo "The provided flag is not identified see $0 -h"
exit;;
:) echo "$OPTARG requires arguments see $0 -h"
exit;;
esac
done
if [[ "$1" != "-a" ]]
then
echo "You have to specify username using -a flag see $0 -h"
fi
useradd -m $user -s $shell
echo "$user":"$password" | chpasswd
echo "The password for the $user is $password";;
d) userdel -f $OPTARG
if [[ $? == 0 ]]
then
echo "user has been removed"
else
echo "There was some error removing the user"
fi;;
h) help
exit;;
/?) echo "$OPTARG option not valid";;
:) echo "$OPTARG requires argument";;
esac
done
Please show your code! I usually process args with case ... in likes :
#!/bin/bash
while [[ $# -gt 0 ]]; do
case $1 in
"-a")
echo "-a is $2"
shift 2;;
"-d")
echo "-d is $2"
shift 2;;
esac
done

Using an "if" statement with curl in terminal?

I'm using this command to get the response code of a page using curl:
curl -s -o /dev/null -w "%{http_code}" 'https://www.example.com'
If the response code is 200, then I want to delete a certain file on my computer. If it isn't 200, nothing should be done.
What's the easiest way to do this?
You can store the result in a shell variable (via command substitution), and then test the value with a simple if and [[ command. For example, in bash:
#!/bin/bash
code=$(curl -s -o /dev/null -w "%{http_code}" 'https://www.example.com')
if [[ $code == 200 ]]; then
rm /path/to/file
# other actions
fi
If all you want is a simple rm, you can shorten it to:
#!/bin/bash
[[ $code == 200 ]] && rm /path/to/file
In a generic POSIX shell, you'll have to use a less flexible [ command and quote the variable:
#!/bin/sh
code=$(curl -s -o /dev/null -w "%{http_code}" 'https://www.example.com')
if [ "$code" = 200 ]; then
rm /path/to/file
fi
Additionally, to test for a complete class of codes (e.g. 2xx), you can use wildcards:
#!/bin/bash
[[ $code == 2* ]] && rm /path/to/file
and the case command (an example here).

PPSS shell script

I made a simple shell script to process mp3 files with SoX.
for f in ./*.mp3; do sox "$f" "${f%%.mp3}S.mp3" silence 1 0.02 1% -1 0.02 1%; done
The syntax should be like this:
sox in.wav out.wav silence 1 0.1 1% -1 0.1 1%
It will remove silence from the files I have in a folder, and create a new file with an "X" at the end (to distinguish from the original). I saved the script in my /bin folder and it works fine.
However, now I want to use it with PPSS, in order to run 8 instances in parallel. I cannot seem to get it working though, in the log file the error I keep getting is this error in the logs:
/usr/local/bin/ppss: line 2283: soxy.sh/Users/marw/Downloads/testfolder//ppss_dir/job_log/_Users_marw_Downloads_testfolder__10_audio_mp3: No such file or directory
Status: FAILURE
Total processing time (hh:mm:ss): 00:00:01
The PPSS syntax should be like this:
|P|P|S|S| Distributed Parallel Processing Shell Script 2.97
usage: /usr/local/bin/ppss [[ -d <sourcedir> | -f <sourcefile> ]] [[ -c '<command> "$ITEM"' ]]
[[ -C <configfile> ]] [[ -j ]] [[ -l <logfile> ]] [[ -p <# jobs> ]]
[[ -q ]] [[ -D <delay> ]] [[ -h ]] [[ --help ]] [[ -r ]] [[ --daemon ]]
Examples:
/usr/local/bin/ppss -d /dir/with/some/files -c 'gzip '
/usr/local/bin/ppss -d /dir/with/some/files -c 'cp "$ITEM" /tmp' -p 2
/usr/local/bin/ppss -f <file> -c 'wget -q -P /destination/directory "$ITEM"' -p 10
I'm new to shell scripting, forgive me if it's a stupid question. My OS is MacOS 10.11.5.
This is what I'm trying with PPSS:
ppss -d /Users/marw/Downloads/testfolder -c 'soxy.sh'
Maybe I have to write the my original script differently? It works fine without PPSS though.
EDIT:
I got a debug log here: http://pastebin.com/wak47rf8
The -c argument has to have a trailing space at the end. This works:
ppss -d /Users/marw/Downloads/testfolder -c 'soxy.sh '
Whereas this does not work:
ppss -d /Users/marw/Downloads/testfolder -c 'soxy.sh'
I got this from the PPSS wiki on Github:
The -c option specifies the command that will be executed by PPSS in
parallel for each file within the directory specified by -d. In this
example the command has a trailing space, which is necessary since the
command will expand to 'gzip example.tar' when executed. If the space
is omitted, an error will occur.

Validate script's argument by file extension?

I am writing a script which you can pass a file name into as an argument and it'll only run if it's a certain file extension.
flac2mp3 "01 Song.flac"
or
flac2mp3 "01 Song.FLAC"
I know there a lot of scripts out there showing you how to convert flac to mp3, but this is my script and I want to learn how to write the script using this method.
It's so I can learn arguments and for when I feel like converting only 1 individual file. (for multiple files I just wrote a for loop with *.flac inside the script)
I just want to learn how to check if the $1 argument contains *.[Ff][Ll][Aa][Cc]
Here's what I cobbled up together from the internet so far (which I know is embarrassingly wrong but I wanted to show what I was going for) :
#!/bin/bash
#flac2mp3
if [ -z $1 ] && [[$1 !=~ *.[Ff][Ll][Aa][Cc]]];then echo "Give FLAC File Name"; exit 0;fi
OUTF=${1%.flac}.mp3
ARTIST=$(metaflac "$1" --show-tag=ARTIST | sed s/.*=//g)
TITLE=$(metaflac "$1" --show-tag=TITLE | sed s/.*=//g)
ALBUM=$(metaflac "$1" --show-tag=ALBUM | sed s/.*=//g)
GENRE=$(metaflac "$1" --show-tag=GENRE | sed s/.*=//g)
TRACKNUMBER=$(metaflac "$1" --show-tag=TRACKNUMBER | sed s/.*=//g)
DATE=$(metaflac "$1" --show-tag=DATE | sed s/.*=//g)
flac -c -d "$1" | lame -m j -q 0 --vbr-new -V 0 -s 44.1 - "$OUTF"
id3 -t "$TITLE" -T "${TRACKNUMBER:-0}" -a "$ARTIST" -A "$ALBUM" -y "$DATE" -g "${GENRE:-12}" "$OUTF"
done
Please and Thank Your for the help.
Try the following code:
shopt -s nocasematch
if [[ $1 == *flac ]]; then
echo "ok"
fi
This is case insensitive.
EDIT
$ LANG=C help shopt
shopt: shopt [-pqsu] [-o] [optname ...]
Set and unset shell options.
Change the setting of each shell option OPTNAME. Without any option
arguments, list all shell options with an indication of whether or not each
is set.
Options:
-o restrict OPTNAMEs to those defined for use with `set -o'
-p print each shell option with an indication of its status
-q suppress output
-s enable (set) each OPTNAME
-u disable (unset) each OPTNAME
Exit Status:
Returns success if OPTNAME is enabled; fails if an invalid option is
given or OPTNAME is disabled.
If you run shopt alone in a shell, you will see al options available :
$ shopt
autocd on
cdable_vars on
cdspell off
checkhash off
checkjobs off
checkwinsize off
cmdhist on
compat31 off
compat32 off
compat40 off
compat41 off
direxpand off
dirspell off
dotglob on
execfail off
expand_aliases on
extdebug off
extglob on
extquote on
failglob off
force_fignore on
globstar on
gnu_errfmt off
histappend on
histreedit off
histverify off
hostcomplete off
huponexit off
interactive_comments on
lastpipe off
lithist off
login_shell off
mailwarn off
no_empty_cmd_completion off
nocaseglob off
nocasematch off
nullglob off
progcomp on
promptvars on
restricted_shell off
shift_verbose off
sourcepath on
xpg_echo off
To know what does all these options :
man bash | less +/'^SHELL BUILTIN COMMANDS'
then search `shopt from within this section.

busybox sh wrapper to add extra functionality

I need a simple busybox sh wrapper which will do:
IF "-Q" PARAMETER IS PROVIDED THEN
acommand ALL PARAMETERS BUT "-Q" 2>&1 1>/dev/null
ELSE
acommand ALL PARAMETERS
FI
Parameters may include spaces.
BTW I want to run the script with busybox sh and it doesn't support arrays.
It's possible to do it all in busybox's ash shell:
#!/bin/sh
for i in "${#}"
do
if [ "$i" = "-Q" ]
then
flagQ=1
else
args="$args \"$i\""
fi
done
if [ "$flagQ" = "1" ]
then
eval acommand "$args" 2>&1 1>/dev/null
else
eval acommand "$args"
fi
This uses bash arrays - but I see from the comments to another answer that the code isn't supposed to run under bash (despite the bash tag originally applied to the question); it is meant to run under the busybox shell.
I'm almost certain it doesn't answer the question because the question is substantially unanswerable given the limitations of busybox. In times past, I have used a custom program I called 'escape' to build up an argument string that can be eval'd to get the original arguments - spaces and all. But that requires support from outside the shell.
This solution only uses 'bash'. I'm not sure it is fully idiomatic bash code, but it works.
#!/bin/bash
i=0
Qflag=0
for arg in "$#"
do
if [ "X$arg" = "X-Q" ]
then Qflag=1
else args[$((i++))]=$arg
fi
done
if [ $Qflag = 1 ]
then exec acommand "${args[#]}" 2>&1 >/dev/null
else exec acommand "${args[#]}"
fi
The first loops builds up an array, args, with the arguments to the script, except it doesn't add '-Q' to the list and records its presence in variable Qflag.
The if statement at the end notes whether Qflag was set to 1, and if so, sends the errors from 'acommand' to standard output and sends regular standard output to /dev/null (which is different from the effect if the I/O redirections are reversed - that would send standard output to /dev/null and send standard error to the same place, forcing silence on 'acommand').
The use of 'exec' is a trivial optimization that simplifies exit status handling in this case.
Tested with 'acommand' that prints its arguments on separate lines:
#!/bin/sh
for arg in "$#"
do echo "$arg"
done
and with command lines such as:
bash wrapper.sh -c -d 'arg with spaces'
which produces the output:
-c
-d
arg with spaces
Obviously, with the I/O redirection in place, there is no output from:
bash wrapper.sh -c -Q -d 'arg with spaces'
However, if you omit the I/O redirection, you get to see the same output.
It's a pity that you need to handle spaces in the arguments otherwise this might work:
#!/bin/sh
Q=0
ARGS=
while [ $# -ge 1 ]; do
case $1 in
-Q)
Q=1
;;
*)
ARGS="$ARGS $1"
;;
esac
shift
done
if [ $Q -eq 1 ] ; then
acommand $ARGS 2>&1 1>/dev/null
else
acommand $ARGS
fi
EDIT:
So this version handles spaces, at the expense of interpreting back-ticks.
#!/bin/busybox ash
Q=0
ARGS=
while [ $# -ge 1 ]; do
case $1 in
-Q)
Q=1
;;
*)
ARGS="$ARGS \"$1\""
;;
esac
shift
done
if [ "$Q" -eq 1 ] ; then
eval acommand $ARGS 2>&1 1>/dev/null
else
eval acommand $ARGS
fi
I think to have a complete solution you are going to have to code it in C, which will be a bit ugly.

Resources