Getting wrong value of passed argument to function in bash script - bash

I am writing bash script given below (Please ignore the capital letters variable names, this is just my test file):
#!/bin/bash
create_nodes_directories(){
HOSTS=(192.168.110.165 192.168.110.166 192.168.110.167)
accounts=('accountnum11' 'accountnum12' 'accountnum13')
for i in "${!HOSTS[#]}"; do
read -r curhost _ < <(hostname -I)
printf 'Enter the key pair for the %s node\n' "${accounts[i]}"
printf "Enter public key\n"
read -r EOS_PUB_KEY
printf "Enter private key\n"
read -r EOS_PRIV_KEY
PRODUCER=${accounts[i]}
args=()
args+=("$curhost")
for j in "${!HOSTS[#]}"; do
if [[ "$i" != "$j" ]]; then
args+=("${HOSTS[$j]}")
else
continue;
fi
done
#echo 'Array before test:'"${args[*]}"
create_genesis_start_file "$EOS_PUB_KEY" "$EOS_PRIV_KEY" "${HOSTS[$i]}" "$PRODUCER" args
create_start_file "$EOS_PUB_KEY" "$EOS_PRIV_KEY" "${HOSTS[$i]}" "$PRODUCER" args
done
}
create_genesis_start_file(){
EOS_PUB_KEY=$1
EOS_PRIV_KEY=$2
CURRENTHOST=$3
PRODUCER=$4
peerags="$5[#]"
peers=("${!peerags}")
echo 'Genesis Currenthost is:'"$CURRENTHOST"
#echo "${peers[*]}"
VAR=""
length=${#peers[#]}
last=$((length - 1))
for i in "${!peers[#]}" ; do
if [[ "$i" == "$last" ]]; then
VAR+="--p2p-peer-address ${peers[$i]}:8888 \\"
else
VAR+=$"--p2p-peer-address ${peers[$i]}:8888 \\"$'\n\t'
fi
done
}
create_start_file(){
EOS_PUB_KEY=$1
EOS_PRIV_KEY=$2
CURRENTHOST=$3
PRODUCER=$4
peerags="$5[#]"
peers=("${!peerags}")
echo 'Start file Currenthost is:'"$CURRENTHOST"
#echo "${peers[*]}"
}
create_nodes_directories
For every iteration of the first for loop, I am displaying the third argument $CURRENTHOST which is passed to functions create_genesis_start_file and create_start_file.
For first iteration, output is:
Genesis Currenthost is:192.168.110.165
Start file Currenthost is:192.168.110.167
Second iteration:
Genesis Currenthost is:192.168.110.166
Start file Currenthost is:192.168.110.167
Third iteration,
Genesis Currenthost is:192.168.110.167
Start file Currenthost is:192.168.110.167
Genesis Currenthost is as expected and Start file Currenthost should be same with it. I am not getting why the Start file Currenthost is always set as 192.168.110.167.
If I remove the below code from create_genesis_start_file it is working fine:
VAR=""
length=${#peers[#]}
last=$((length - 1))
for i in "${!peers[#]}" ; do
if [[ "$i" == "$last" ]]; then
VAR+="--p2p-peer-address ${peers[$i]}:8888 \\"
else
VAR+=$"--p2p-peer-address ${peers[$i]}:8888 \\"$'\n\t'
fi
done
I am not getting the exact problem why the variable value is getting changed? Please help.

The "$5[#]" looks odd to me. You can't use a scalar $5 as if it were an array.
It seems that you want to pass a whole array as parameter. Since bash does not have a native way to do this, I suggest that on the calling side, you pass "${args[#]}" as parameter, and inside your function, you do a
shift 4
peers=( "$#" )
Another possibility, which however violates the idea of encapsulation, is to treet peers as a global variable, which is accessible to all functions. With this approach, you would on the caller side collect the information already in the variable peers instead of args.
From a programming style, global variables (accross function boundaries) are usually disliked for good reasons, but in my personal opinion, if you just do simple shell scripting, I would find it an acceptable solution.

Related

Getting piped data to functions

Example output
Say I have a function, a:
function a() {
read -r VALUE
if [[ -n "$VALUE" ]]; then # empty variable check
echo "$VALUE"
else
echo "Default value"
fi
}
So, to demonstrate piping to that function:
nick#nick-lt:~$ echo "Something" | a
Something
However, piping data to this function should be optional. So, this should also be valid. and give the following output:
nick#nick-lt:~$ a
Default value
However, the function hangs, as the read command waits for data from stdin.
What I've tried
Honestly not a lot, because I don't know much about this, and searching on Google returned very little.
Conceptually, I thought there might be a way to "push" an empty (or whitespace, whatever works) value to the stdin stream, so that even empty stdin at least has this value appended/prepended, triggering read and then simply trim off that first/last character. I didn't find a way to do this.
Question
How can I, if possible, make both of the above scenarios work for function a, so that piping is optional?
EDIT: Apologies, quickly written question. Should work properly now.
One way is to check whether standard input (fd 0) is a terminal. If so, don't read, because that will cause the user to have to enter something.
function a() {
value=""
if [ \! -t 0 ] ; then # read only if fd 0 is a pipe (not a tty)
read -r value
fi
if [ "$value" ] ; then # if nonempty, print it!
echo "$value"
else
echo "Default value"
fi
}
I checked this on cygwin: a prints "Default value" and echo 42 | a prints "42".
Two issues:
Syntactic, You need a space, before closing ]]
Algorithmic, You need the -n (non-zero length) variable test, not -z (zero length)
So:
if [[ -n "$VALUE" ]]; then
Or simply:
if [[ "$VALUE" ]]; then
As [[ is a shell builtin, you don't strictly need the double quotes:
if [[ $VALUE ]]; then
Also refrain from using all uppercases as variable name, as these are usually used for denoting environment variables, and your defined one might somehow overwrite already existing one. So use lowercase variable name:
if [[ $value ]]; then
unless you are export-ing your variable, and strictly need it to be uppercased, also make sure it is not overwriting any already existing one.
Also, i would add a timeout to read e.g. -t 5 for 5 seconds, and if no input is entered, print the default value. Also change the function name to something more meaningful.
Do:
function myfunc () {
read -rt5 value
if [[ "$value" ]]; then
echo "$value"
else
echo "Default value"
fi
}
Example:
$ function myfunc () { read -rt5 value; if [[ "$value" ]]; then echo "$value"; else echo "Default value"; fi ;}
$ myfunc
Default value
$ echo "something" | myfunc
something
$ myfunc
foobar
foobar

Bash indirect variable assignment inside a function

I have a script where the user input needs to be evaluated several times, the solution im working on is to put the evaluation bits into a function, and simply call the function every time i need to evaluate the input.
The problem is though that when im trying to update the $1 variable (that referes to the first variable parameter of the function) I get the error message "$VARIABLE command not found".
Here is the code:
function input_handler() {
if is_integer $1; then
selid="$1 -1"
if [[ "$1" -le "0" ]]; then
echo "Please use a simple positive number!"
else
if [[ "$1" -le "${#array[*]}" ]]; then
eval $1="${array[selid]}"
echo "Ok, moving on..."
else
echo "That number seems too large, try again?"
fi
fi
else
if [ -e $2/$1 ]; then
echo "Ok, moving on..."
else
echo "That item is not on the list, try again!"
fi
fi
}
And this command:
input_handler $doctype $docpath
Gives this output:
5
./test: line 38: 5=sun: command not found
Ok, moving on...
Now this is almost correct, but what im after is doctype=sun, not 5=sun, in other words I need the $1 variable name not its value. Changing the line eval $1="${array[selid]}" to eval doctype="${array[selid]}" fixes this particular instance. But this does not fix my problem as I need to run this function on different variables with different names.
Maybe not fully understand what you want achieve, but check the next example:
weirdfunc () {
echo " weirdfunc: variable name is: $1"
echo " weirdfunc: variable value is: ${!1}"
eval "$1=$(( ${!1} + 1))" #assign
}
myvar="5"
echo "the value of myvar before: $myvar"
weirdfunc myvar #call with the NAME not with the value, so NOT weridfunc $myvar
echo "the value of myvar after: $myvar"
In short - when you want to do anything with the variable NAME in an called function, you should pass the NAME of the variable and NOT his value. So call the function
somefunc NAME
instead of
somefunc $NAME
and use the above constructs to get the name and value inside the function.
You can't update the value of $1 with a traditional assignment, but you can update the positional parameters with the set builtin.
$ f() { echo "$#"; set -- a b c; echo "$#"; echo $2; }
$ f 1 2 3
1 2 3
a b c
b
Just keep in mind this will wipe out all the positional parameters you don't re-set each time, so you'll need to set $2 if you want to keep it around.
Your best bet is probably to assign the values in the positional parameters to names and just use names from then on.
If you protect the variable name, Bash will evaluate and assign to $1 instead of try to execute $1=value.
eval "$1"=${array[selid]}
Positional parameters are read-only. So what you want to do is not possible. You should do something like
foo=$1
and then work with $foo instead of $1

Make use of variable from while read loop

In my bash script I use while read loop and a helper function fv():
fv() {
case "$1" in
out) echo $VAR
;;
* ) VAR="$VAR $1"
;;
esac
}
cat "$1" | while read line
do
...some processings...
fv some-str-value
done
echo "`fv out`"
in a hope that I can distil value from while read loop in a variable accessible in rest of the script.
But above snippet is no good, as I get no output.
Is there easy way to solve this - output string from this loop in a variable that would be accessible in rest of the script - without reformatting my script?
As no one has explained to you why your code didn't work, I will.
When you use cat "$1" |, you are making that loop execute in a subshell. The VAR variable used in that subshell starts as a copy of VAR from the main script, and any changes to it are limited to that copy (the subshell's scope), they don't affect the script's original VAR. By removing the useless use of cat, you remove the pipeline and so the loop is executed in the main shell, so it can (and does) alter the correct copy of VAR.
Replace your while loop by while read line ; do ... ; done < $1:
#!/bin/bash
function fv
{
case "$1" in
out) echo $VAR
;;
* ) VAR="$VAR $1"
;;
esac
}
while read line
do
fv "$line\n"
done < "$1"
echo "$(fv out)"
Stop piping to read.
done < "$1"

compound comparisons in bash

can anybody explain why the following bash code involving compound operators is not behaving as expected? basically, nothing enters the if statement inside the for loop but i am passing it correct parameters that should return something by running:
./my_bash_script 20100101 20120101
dates.txt is a list of all days since 2000
#!/bin/bash
old_IFS=$IFS
IFS=$'\n'
lines=($(cat dates.txt)) # array
IFS=$old_IFS
for (( i=1; i<${#lines[#]}; i++ ))
do
if [[ ${line[$i]} -ge $1 && ${line[$i]} -le $2 ]]; then
echo 0 > ${line[$i]} # redirect to file
echo ${line[$i]}
fi
done
The problem is that you've declared an array named lines, but then you try to access it as though it were named line. You need to change every occurrence of ${line[$i]} to ${lines[$i]}.
Better yet, you can dispense with the arithmetic for-loop, and write:
for line in "${lines[#]}" ; do
which will let you refer to the line as $line or "$line" rather than as ${lines[$i]}.
(By the way, how come you have that logic to modify $IFS? It seems like its default value would work just as well.)

Nested loops seem to skip over second loop in Bash

I am trying to write a script that:
a) reads the content of a .csv file
b) sets a variable to the value in the first position (ie to the left of the comma)
c) compare the variable value to each position in an array. If the value is in the array execute one command, if it isn't, insert that value into the first available slot in the array.
The .csv file is in the format:
co:7077,he17208am3200816internet.pdf,he17208am3200917internet.pdf
co:7077,he17208am3200817internet.pdf,he17208am3200918internet.pdf
co:7077,he17208am3200818internet.pdf,he17208am3200919internet.pdf
co:7077,he17208am3200819internet.pdf,he17208am3200915internet.pdf
co:7162,tra210051internet.pdf,tra21005101internet.pdf
co:7162,tra210051appinternet.pdf,tra21005102internet.pdf
co:7178,tra4157l11201021internet.pdf,tra4158l11201021internet.pdf
co:7178,tra4157l11201022internet.pdf,tra4158l11201022internet.pdf
My script so far looks like:
#!/bin/bash
declare -a array
anum=0
src=source.csv
pid=0
while read line;
do
pid=$( echo $line | awk '{print$1}' FS=",")
for n in "${array[#]}";
do
if [[ "$pid" = "$n" ]] ;
then
echo Duplicate value: "$pid";
else
array[$anum]="$pid"
anum=$(( $anum +1 ))
fi
done
done < $src
echo ${array[#]}
When the script is executed the pid is successfully set and reset with each iteration of the while loop, but apparently the nested for loop is never ran.
From my google'ing I suspect it has something to do with the pipe in pid line, but I'll be buggered if I can figure out how to make it work.
Any help is greatly appreciated.
You're not populating your array. The for loop is never executed because the array is empty.
Set a flag in the else clause instead of adding the array element there. After your for loop if the flag is set, add the array element. Don't forget to unset the flag.
You can do array[anum++] without the next line or (( anum++ )) instead of anum=$(($anum + 1)).
Also: while IFS=, read -r pid discard if you don't need the rest of the line (you could do it a little differently if you need it). Doing this, you won't need the echo and awk.
why did you use double square brackets? and also you used a single equals rather than double in the if?
try these one-liners...
$ if [ "a" == "b" ] ; then echo hello ; fi
$ if [ "a" == "a" ] ; then echo hello ; fi

Resources