Bash scripting - nested variables - bash

I currently encountered a tricky problem on which I couldn't find any solution yet.
I wrote a script like this:
#!/bin/sh
x=1
while [ "$x" -le $# ]; do
echo "$x"'. Argument is: ' "\$$x"
x="$(( $x + 1 ))"
done
I suggested that the shell would evaluate the expression "\$$x" after expanding the variables to get access to the argument on position x but the output is:
1. Argument is: $1
Please help. Thx in advance.

Here is the fix
$ cat a.sh
#!/bin/sh
x=1
while [ "$x" -le $# ]; do
echo "$x"'. Argument is: ' "${!x}" # If you need indirect expansion, use ${!var} is easier way.
x="$(( $x + 1 ))"
done
Test result
$ sh a.sh a b c
1. Argument is: a
2. Argument is: b
3. Argument is: c

This code should work:
#!/bin/sh
x=0
args=($#)
while [ "$x" -lt $# ]; do
echo "$x"'. Argument is: ' "${args[${x}]}"
x="$(( $x + 1 ))"
done

Related

Bash script if statement not working

I'm trying to write a script that adds three specified arguments together, and if there are no arguments outputs: "No arguments".
The trouble is that "No arguments" is always output even when there are three arguments.
I am very new to shell script.
Here is my script:
#!/bin/sh
if [[("$#"==0)]]; then
echo "No arguments specified"
exit 1
fi
sum=0
sum=$(expr $1 + $2 + $3)
echo "$sum"
exit 0
Either change your shebang to #!/bin/bash and use
if (( $# == 0 )); then
or use the POSIX-compatible [:
if [ $# -eq 0 ]; then
Don't forget that [ and [[ are both commands, not syntax, so as with any other command, you need to separate the arguments you pass to the command with spaces.
If you are using bash features, such as [[, you should always use the #!/bin/bash shebang, as otherwise you will run into problems.
As pointed out in the comments below the other answer, it is possibly a better idea to check that you have been passed three arguments:
#!/bin/bash
if (( $# < 3 )); then
echo "Insufficient number of arguments specified"
exit 1
fi
sum=$(( $1 + $2 + $3 ))
echo "$sum"
I have made a couple of other changes to your script, such as not initialising sum to 0 and using the more modern $(( )) to evaluate the sum of the variables.
I guess what you ask for is:
if [ $# -eq 0 ];

How do I rewrite a bash 'while' loop as a 'for' loop?

This is my bash scripting code so I want to know How to Rewrite the below Bash script using a “for” loop instead of the “while” loop.
#!/bin/bash
if [ $# -gt 0 ]; then
a=0;
if [ -f RandNos ]; then
rm RandNos;
fi
while [ $a -lt $1 ]
do
a='expr $a + 1';
myrand=$RANDOM;
if [ "$2" "1"]; then
echo "No. $a ==> $myrand";
fi
echo $myrand>>RandNos
done
else
echo "please use with an argument..."
fi
Thanks.
The short of it: for counter-based loops, use the C-like form of the for loop:
for (( a = 0; a < $1; a++ )); do
# ... use $a
done
(This replaces while [ $a -lt $1 ]; do a='expr $a + 1' ...; done.)
See below for more on the rules that apply inside (( ... )).
As for the rest of your code:
Conditional [ "$2" "1"] is broken: it's missing the mandatory space before ]
With that fixed, it'll only work if $2 expands to a unary test operator such as -n.
Perhaps you meant if [[ -z $myrand ]]; then, to check if $RANDOM resulted in a nonempty string?
a='expr $a + 1' - which you don't need anymore with the for loop - doesn't actually invoke expr, because you're using single quotes - you'd need backticks (`) instead, or, preferably, the modern equivalent: $(expr $a + 1). However, with arithmetic evaluation, this could be simplified to (( ++a )).
[ ... ] conditionals work in bash, but they're provided for POSIX compatibility - use [[ ... ]] as the bash-specific alternative, which is more robust, has more features, and is faster.
bash statements only need terminating with ; if you place multiple on a single line
Note that bash considers do ... and then ... separate statements, hence you often see if ...; then and for ...; do.
In general, I encourage you to syntax-check your shell code at http://shellcheck.net - it's a great tool for detecting syntax problems.
Note how different rules apply inside (( ... )) compared to elsewhere in bash:
spaces around the = in the variable assignment are allowed.
referencing a variable without the $ prefix (a++) is allowed.
< performs numerical comparison (whereas inside [[ ... ]] it's lexical) -i.e., it's the more natural equivalent to -lt inside [ ... ] or [[ ... ]].
several other mathematical and even bit-wise operators are supported
...
All these different rules apply when bash operates in an arithmetic context, which applies to (( ... )), $(( ... )), array subscripts, and other cases.
For all the rules, run man bash and read the ARITHMETIC EVALUATION section.
Simply rewriting it with a for loop results in:
#!/bin/bash
if [ $# -gt 0 ]; then
if [ -f RandNos ]; then
rm RandNos;
fi
lim=$(expr $1 - 1)
as=$(seq 0 $lim)
for a in $as
do
a='expr $a + 1';
myrand=$RANDOM;
if [ "$2" "1"]; then # <- Caveat: conditional is BROKEN
echo "No. $a ==> $myrand";
fi
echo $myrand>>RandNos
done
else
echo "please use with an argument..."
fi
But there are several things wrong with the script anyhow. Like the last if statement.
if [ $# -lt 1 ];then
echo "First argument must be number".
exit 1;
fi
for a in `seq $1`
do
...
done
Several things can be improved:
#!/bin/bash
if (( $# )); then # anything but 0 is true
rm -f RandNos # remove if existing, otherwise fail silently
for ((a=0; a<$1; a++)); do
myrand=$RANDOM
# what is the intention here?
(( $2 > 1 )) && echo "No. $a ==> $myrand"
echo "$myrand" >> RandNos
done
else
echo "please use with an argument..."
fi
not sure what your intention was with the [ "$2" "1" ] expression. it is probably not what I made from it.
for ((a=1; a<=$1; a++)); do
may reflect your intended logic better, as you use $a for output only after incrementing it. as pointed out and corrected by #mklement0
!/bin/bash
if [ $# -gt 0 ]; then
a=0;
if [ -f RandNos ]; then
rm RandNos;
fi
for (( i=$a; i<$1; i++ ))
do
myrand=$RANDOM;
if [ "$2" = "1" ]; then
echo "No. $a ==> $myrand";
fi
echo $myrand >> RandNos
done
else
echo "please use with an argument..."
fi

How to add the command line inputs

I need to perform a calculation(addition/multiplication) using the command line input.
For an example: I'm executing the below ./calculation.sh 1 2 3 4 5. It has to sum up the output as 15. Any idea to this ? I've tried with the below logic but couldn't make it.
set -x
while [ $# -gt 0 ]
do
expr $1 + 1
shift
done
OUTPUT=0
for i in $*; do
OUTPUT=$(($OUTPUT + $i))
done
echo $OUTPUT
You need to make use of a variable to save the result of expr. Moreover, +1 doesn't seem to make much sense. You probably wanted to replace that with the variable itself.
You need to print the variable at the end.
Try:
set -x
res=0
while [ $# -gt 0 ]
do
res=`expr $1 + $res`
shift
done
echo $res
Try
set -x
sum=0
while [ $# -gt 0 ]
do
sum=$(expr "$sum" + "$1")
shift
done
echo "sum: $sum"
And it's simpler in bash:
sum=0
for i; do
(( sum += i ))
done
echo "sum: $sum"

How to skip the first argument in $#?

My code:
#!/bin/bash
for i in $#;
do echo $i;
done;
run script:
# ./script 1 2 3
1
2
3
So, I want to skip the first argument and get:
# ./script 1 2 3
2
3
Use the offset parameter expansion
#!/bin/bash
for i in "${#:2}"; do
echo $i
done
Example
$ func(){ for i in "${#:2}"; do echo "$i"; done;}; func one two three
two
three
Use shift command:
FIRST_ARG="$1"
shift
REST_ARGS="$#"
Look into Parameter Expansions in the bash manpage.
#/bin/bash
for i in "${#:2}"
do echo $i
done
You could just have a variable testing whether it's the first argument with something like this (untested):
#!/bin/bash
FIRST=1
for i in $#
do
if [ FIRST -eq 1 ]
then
FIRST=0
else
echo $i
fi
done

Check existence of input argument in a Bash shell script

I need to check the existence of an input argument. I have the following script
if [ "$1" -gt "-1" ]
then echo hi
fi
I get
[: : integer expression expected
How do I check the input argument1 first to see if it exists?
It is:
if [ $# -eq 0 ]
then
echo "No arguments supplied"
fi
The $# variable will tell you the number of input arguments the script was passed.
Or you can check if an argument is an empty string or not like:
if [ -z "$1" ]
then
echo "No argument supplied"
fi
The -z switch will test if the expansion of "$1" is a null string or not. If it is a null string then the body is executed.
It is better to demonstrate this way
if [[ $# -eq 0 ]] ; then
echo 'some message'
exit 1
fi
You normally need to exit if you have too few arguments.
In some cases you need to check whether the user passed an argument to the script and if not, fall back to a default value. Like in the script below:
scale=${2:-1}
emulator #$1 -scale $scale
Here if the user hasn't passed scale as a 2nd parameter, I launch Android emulator with -scale 1 by default. ${varname:-word} is an expansion operator. There are other expansion operators as well:
${varname:=word} which sets the undefined varname instead of returning the word value;
${varname:?message} which either returns varname if it's defined and is not null or prints the message and aborts the script (like the first example);
${varname:+word} which returns word only if varname is defined and is not null; returns null otherwise.
Try:
#!/bin/bash
if [ "$#" -eq "0" ]
then
echo "No arguments supplied"
else
echo "Hello world"
fi
Only because there's a more base point to point out I'll add that you can simply test your string is null:
if [ "$1" ]; then
echo yes
else
echo no
fi
Likewise if you're expecting arg count just test your last:
if [ "$3" ]; then
echo has args correct or not
else
echo fixme
fi
and so on with any arg or var
Another way to detect if arguments were passed to the script:
((!$#)) && echo No arguments supplied!
Note that (( expr )) causes the expression to be evaluated as per rules of Shell Arithmetic.
In order to exit in the absence of any arguments, one can say:
((!$#)) && echo No arguments supplied! && exit 1
Another (analogous) way to say the above would be:
let $# || echo No arguments supplied
let $# || { echo No arguments supplied; exit 1; } # Exit if no arguments!
help let says:
let: let arg [arg ...]
Evaluate arithmetic expressions.
...
Exit Status:
If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise.
I often use this snippet for simple scripts:
#!/bin/bash
if [ -z "$1" ]; then
echo -e "\nPlease call '$0 <argument>' to run this command!\n"
exit 1
fi
More modern
#!/usr/bin/env bash
if [[ $# -gt 0 ]]
then echo Arguments were provided.
else echo No arguments were provided.
fi
If you'd like to check if the argument exists, you can check if the # of arguments is greater than or equal to your target argument number.
The following script demonstrates how this works
test.sh
#!/usr/bin/env bash
if [ $# -ge 3 ]
then
echo script has at least 3 arguments
fi
produces the following output
$ ./test.sh
~
$ ./test.sh 1
~
$ ./test.sh 1 2
~
$ ./test.sh 1 2 3
script has at least 3 arguments
$ ./test.sh 1 2 3 4
script has at least 3 arguments
As a small reminder, the numeric test operators in Bash only work on integers (-eq, -lt, -ge, etc.)
I like to ensure my $vars are ints by
var=$(( var + 0 ))
before I test them, just to defend against the "[: integer arg required" error.
one liner bash function validation
myFunction() {
: ${1?"forgot to supply an argument"}
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
add function name and usage
myFunction() {
: ${1?"forgot to supply an argument ${FUNCNAME[0]}() Usage: ${FUNCNAME[0]} some_integer"}
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
add validation to check if integer
to add additional validation, for example to check to see if the argument passed is an integer, modify the validation one liner to call a validation function:
: ${1?"forgot to supply an argument ${FUNCNAME[0]}() Usage: ${FUNCNAME[0]} some_integer"} && validateIntegers $1 || die "Must supply an integer!"
then, construct a validation function that validates the argument, returning 0 on success, 1 on failure and a die function that aborts script on failure
validateIntegers() {
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
return 1 # failure
fi
return 0 #success
}
die() { echo "$*" 1>&2 ; exit 1; }
Even simpler - just use set -u
set -u makes sure that every referenced variable is set when its used, so just set it and forget it
myFunction() {
set -u
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
In my case (with 7 arguments) the only working solution is to check if the last argument exists:
if [[ "$7" == '' ]] ; then
echo "error"
exit
fi

Resources