How to catch missing argument in function mistake? - linux

I have just made the following mistake, where I am passing an argument to a function which is empty.
var1="ok"
var2=$notDefined
func $var1 $var2
func() {
var1=$1
var2=$2
echo $var1
echo $var2
}
For each argument in the function I could do
if [ -z $1 ]; then echo "Empty argument"; fi
But is there a more generic method to do this, so it is easy reusable, and would perhaps even tell the variable name that is empty?

You can stop whole script by set -u. It will fail if you try to use unset variable. It is very general approach.
Bash will output following localized message to standard error:
bash: x: unbound variable

You want to use the ? bash variable substitution operator:
var1=${1:?"undefined!"}
If $1 exists and isn't null, var1 is set to its value, otherwise bash prints 1 followed by "undefined!" and aborts the current command or script. This syntax can used for any bash variable.

In your case the empty variables are created, because there are too few arguments to the function.
You can get the number of passed arguments via $#. All variables that use $n with a higher number n must then be empty. You could check for a sufficiently high number of arguments at the beginning of your function.

#!/bin/bash
var1="ok"
var2=$notDefined
func() {
if [[ $# -ge 2 ]]; then
var1=$1
var2=$2
echo $var1
echo $var2
else
echo "Missing values"
fi
}
func $var1 $var2
Here it is running
./test.sh
Missing values
Here it is with two values:
#!/bin/bash
var1="ok"
var2="dokie"
func() {
if [[ $# -ge 2 ]]; then
var1=$1
var2=$2
echo $var1
echo $var2
else
echo "Missing values"
fi
}
func $var1 $var2
Results with:
./test.sh
ok
dokie

Related

How to check number of arguments using a bash script?

How do I format in a script the number of arguments passed through a bash script? This what I have currently that works:
#!/bin/bash
echo "$# parameters"
echo "$#"
But I wanted to format is using a function but everytime I run it, it comes back as 0 parameters:
#!/bin/bash
example()
{
echo "$# parameters"; echo "$#";
}
example
Am I thinking about this incorrectly?
You are not passing the arguments to your function.
#! /bin/bash
EXE=`basename $0`
fnA()
{
echo "fnA() - $# args -> $#"
}
echo "$EXE - $# Arguments -> $#"
fnA "$#"
fnA five six
Output:
$ ./bash_args.sh one two three
bash_args.sh - 3 Arguments -> one two three
fnA() - 3 args -> one two three
fnA() - 2 args -> five six
It's the POSIX standard not to use the function keyword. It's supported in bash for ksh compatibility.
EDIT: Quoted "$#" as per Gordon's comment - This prevents reinterpretation of all special characters within the quoted string
The second one should work. Following are few similar examples I use every day that has the same functionality.
function dc() {
docker-compose $#
}
function tf(){
if [[ $1 == "up" ]]; then
terraform get -update
elif [[ $1 == "i" ]]; then
terraform init
else
terraform $#
fi
}
function notes(){
if [ ! -z $1 ]; then
if [[ $1 == "header" ]]; then
printf "\n$(date)\n" >> ~/worknotes
elif [[ $1 == "edit" ]]; then
vim ~/worknotes
elif [[ $1 == "add" ]]; then
echo " • ${#:2}" >> ~/worknotes
fi
else
less ~/worknotes
fi
}
PS: OSX I need to declare function you may not need that on other OSes like Ubuntu

How can I look up a variable by name with #!/bin/sh (POSIX sh)?

f1="filename1";
i=1;
c=f$i
echo $c
What shell command should I use so that echo $c returns "filename1" as the output?
Use variable indirection.
#!/bin/bash
f1="filename1";
i=1;
c=f$i
echo ${!c}
It works in bash ( GNU bash, version 4.1.2(1)-release ). I have not tried in other shells.
You can use eval to "nest" variable substitutions.
f1="filename1";
i=1;
eval c=\${f$i}
echo $c
Reusable function
# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
if [ -z "${1-}" ] || [ $# -ne 1 ]; then
printf 'var_expand: expected one argument\n' >&2;
return 1;
fi
eval printf '%s' "\"\${$1?}\""
}
It warns when given:
Null input
More than one argument
A variable name which isn't set
Avoid intensive operations like eval. Use an associative array.
#!/bin/bash
typeset -A c
c[f1]=filename1
i=1
echo ${c[f$i]}

How to check if there is a parameter provided in bash?

I wanted to see if there is a parameter provided. But i don't know why this code wont work. What am i doing wrong?
if ![ -z "$1" ]
then
echo "$1"
fi
Let's ask shellcheck:
In file line 1:
if ![ -z "$1" ]
^-- SC1035: You need a space here.
In other words:
if ! [ -z "$1" ]
then
echo "$1"
fi
If, as per your comment, it still doesn't work and you happen to be doing this from a function, the function's parameters will mask the script's parameters, and we have to pass them explicitly:
In file line 8:
call_it
^-- SC2119: Use call_it "$#" if function's $1 should mean script's $1.
You could check the number of given parameters. $# represents the number of parameters - or the length of the array containing the parameters - passed to a script or a function. If it is zero then no parameters have been passed. Here is a good read about positional parameters in general.
$ cat script
#!/usr/bin/env bash
if (( $# == 0 )); then
echo "No parameters provided"
else
echo "Number of parameters is $#"
fi
Result:
$ ./script
No parameters provided
$ ./script first
Number of parameters is 1
$ ./script first second
Number of parameters is 2
If you would like to check the parameters passed to the script with a function then you would have to provide the script parameters to the function:
$ cat script
#!/usr/bin/env bash
_checkParameters()
{
if (( $1 == 0 )); then
echo "No parameters provided"
else
echo "Number of parameters is $1"
fi
}
_checkParameters $#
This will return the same results as in the first example.
Also related: What are the special dollar sign shell variables?

How to display argument in shell script

I have a shell script called displayArg.sh This is how I intend to run it-
./displayArg hello
and the output is entered arg is hello
The following is the script-
if [ $1 == "" ]; then
default="Default"
echo "no value is given. Output is $default"
else
value=$?
echo "entered arg is $value" #I know I am wrong in these 2 lines, but not sure how to fix it
fi
Kindly bear with me. I'm new to Shell scripting
You want:
value="$1"
($? is the status of the last command, which is 1 because the test command is what was executed last.)
Or you can simplify to:
if [ "$1" == "" ]
then
echo "no value is given. Output is Default"
else
echo "entered arg is $1"
fi
Note the quotes around "$1" in the test. If the string is empty, you get a syntax error. Your alternative with bash is to use a [[ $1 == "" ]] test.

How to enable or disable multiple "echo statements" in bash ecript

I have bash script where i have echo before every command showing what is happening.
But i need to disbale echo when setting as cron job and then enable again if do some testing.
i find it very hard to go to each line and then add/remove comment
is there anything which i can include at top something like
enable echo or disable echo
so that i don't have to waste time
The absolute easiest would be to insert the following line after the hashbang line:
echo() { :; }
When you want to re-enable, either delete the line or comment it out:
#echo() { :; }
If you're not using echo but printf, same strategy, i.e.:
printf() { :; }
If you absolutely need to actually echo/printf something, prepend the builtin statement, e.g.:
builtin echo "This 'echo' will not be suppressed."
This means that you can do a conditional output, e.g.:
echo () {
[[ "$SOME_KIND_OF_FLAG" ]] && builtin echo $#
}
Set the SOME_KIND_OF_FLAG variable to something non-null, and the overridden echo function will behave like normal echo.
EDIT: another alternative would be to use echo for instrumenting (debugging), and printf for the outputs (e.g., for piping purposes). That way, no need for any FLAG. Just disable/enable the echo() { :; } line according to whether you want to instrument or not, respectively.
Enable/Disable via CLI Parameter
Put these lines right after the hashbang line:
if [[ debug == "$1" ]]; then
INSTRUMENTING=yes # any non-null will do
shift
fi
echo () {
[[ "$INSTRUMENTING" ]] && builtin echo $#
}
Now, invoking the script like this: script.sh debug will turn on instrumenting. And because there's the shift command, you can still feed parameters. E.g.:
Without instrumenting: script.sh param1 param2
With instrumenting: script.sh debug param1 param2
The above can be simplified to:
if [[ debug != "$1" ]]; then
echo () { :; }
shift
fi
if you need the instrumenting flag (e.g. to record the output of a command to a temp file only if debugging), use an else-block:
if [[ debug != "$1" ]]; then
echo () { :; }
shift
else
INSTRUMENTING=yes
fi
REMEMBER: in non-debug mode, all echo commands are disabled; you have to either use builtin echo or printf. I recommend the latter.
Several things:
Don't use echo at all
Instead use set -xv to set debug mode which will echo each and every command. You can set PS4 to the desired prompt: for example PS4='$LINENO: ' will print out the line number on each line. In BASH, I believe it's the same. Then, you don't have to clean up your script. To shut off, use set +xv.
Example:
foo=7
bar=7
PS4='$LINENO: '
set -xv #Begin debugging
if [ $foo = $bar ]
then
echo "foo certainly does equal bar"
fi
set +xv #Debugging is off
if [ $bar = $foo ]
then
echo "And bar also equals foo"
fi
Results:
$ myprog.sh
if [ $foo = $bar ]
then
echo "foo certainly does equal bar"
fi
5: [ 7 = 7 ]
7: echo 'foo certainly does equal bar'
foo certainly does equal bar
set +xv #Debugging is off
And bar also equals foo
Use a function
Define a function instead of using echo:
Example:
function myecho {
if [ ! -z "$DEBUG" ]
then
echo "$*"
fi
}
DEBUG="TRUE"
my echo "Will print out this line"
unset DEBUG
myecho "But won't print out this line"
Use the nop command
The colon (:) is the nop command in BASH. It doesn't do anything. Use an environment variable and define it as either echo or :. When set to a colon, nothing happens. When set to echo, the line prints.
Example:
echo=":"
$echo "This line won't print"
echo="echo"
$echo "But this line will."
Building on Matthew's answer, how about something like this:
myEcho = "/bin/true"
if [ ! "$CRON" ]: then
myEcho = "/bin/echo"
fi
and then use $myEcho instead of echo in your script?
You can do one better. If you setup your crontab as detailed in another answer, you can then check if you are running in cron and only print if you are not. This way you don't need to modify your script at all between different runs.
You should then be able to use something like this (probably doesn't quite work, I'm not proficient in bash):
if [ ! "$CRON" ]; then
echo "Blah blah"
fi
Try set -v at the top to echo each command. To stop echoing change it to set +v.
Not sure if I miss the below solution to use a variable (e.g. debug) at the start of the bash script.
Once you set the debug=true, any conditional-if will enable or disable multiple “echo statements” in bash script.
typeset debug=false # set to true if need to debug
...
if [ $debug == "true" ]; then
echo
echo "Filter"
read
fi
...
if [ $debug == "true" ]; then
echo
echo "to run awk"
fi
Couldn't post a code block in a comment, so I'll post this as an answer.
If you're a perfectionist (like I am) and don't want the last set +x line to be printed... and instead print Success or FAIL, this works:
(
set -e # Stop at first error
set -x # Print commands
set -v # Print shell input lines as they are read
git pull
// ...other commands...
) && echo Success || echo FAIL
It will create a sub process, though, which may be an overkill solution.
If you're running it in cron, why not just dump the output? Change your crontab entry so that it has > /dev/null at the end of the command, and all output will be ignored.

Resources