Accessing Shell parameters inside functions - bash

So I'm playing around with a simple bash program, simply to learn the basics of shell and bash. The idea is a tiny password generator that saves passwords to a .txt file. Keep in mind, this is simply something to learn shell scripting - of course I understand the premise is unsafe :)
For example, I want to be able to write
./script.sh facebook generate
Which would generate a simple string and then if I confirm - save the password and site name to the .text file.
Anyways, the problem I am having is - I'm unable to retrieve the $1 and $2 parameters on the line in which I am echoing the text to the txt file (see below). I've put a comment on the line that I can't get to work. I just get blanks in the .txt file.
Can anyway help and give some guidance on accessing the parameters inside functions?
function generate() {
PASS="9sahdoasndkasnda89zls"
echo $PASS
}
function storeConfirm() {
/bin/echo -n "Do you want to store this password? Y/N "
read answer
if [ "$answer" == "Y" ]
then
echo "--------" >> pwstore.txt;
echo $1 "=" $2 >> pwstore.txt; #This line doesn't work, can't seem to access the $1 and $2 params
echo "Successfully stored for you"
else
echo "Ok... have it your way"
fi
}
if [[ $2 == "generate" ]]; then
generate
echo "for site=" $1
storeConfirm
fi

Since you can pass parameters to a function in the same way you can pass parameters to a script you have to make sure the script parameters get "forwarded" to the function. In other words - a function has its own "parameter scope". An example:
$ cat script
#!/usr/bin/env bash
_aFunction(){
echo "Parameter 1: ${1}"
echo "Parameter 2: ${2}"
}
_aFunction
_aFunction "$1" "$2"
_aFunction One Two
$ ./script 1 2
Parameter 1:
Parameter 2:
Parameter 1: 1
Parameter 2: 2
Parameter 1: One
Parameter 2: Two

Related

Bash loop dynamic variable from source file

I got a config file called user.conf that contains this list:
USER1_USERNAME="John"
#USER2_USERNAME="Mike"
USER3_USERNAME="David"
USER4_USERNAME="James"
USER5_USERNAME="Jenny"
Notice that user can comment out that line USER2_USERNAME so, only 4 users are used in source file in bash script test.sh here:
#!/bin/bash
#source the username
source "user.conf"
n=1
while :; do
((n++))
if [ -n "${USER${n}_USERNAME}"]; then
echo "This variable USER${n}_USERNAME is set: ${USER${n}_USERNAME}"
else
echo "This variable USER${n}_USERNAME is not set"
fi
done
I want to display which variable is set with its value and skip the commented variables but at this point my code just display an error:
./test.sh: line 8: ${USER${n}_USERNAME}: bad substitution
Is it possible to loop that variable like above?
Also, user can define much more USER in that user.conf, for example
USER6_USERNAME="George "
Expected output:
This variable USER1_USERNAME is set to John
This variable USER2_USERNAME is not set
This variable USER3_USERNAME is set to David
This variable USER4_USERNAME is set to James
This variable USER5_USERNAME is set to Jenny
You can use bash variable substitution like this:
#!/usr/bin/env bash
source "user.conf"
for i in "${!USER#}"
do
echo $i ${!i}
done
Like your code, this first sources the users file, as it looks like a bash script. This of course has the potentially dangerous side effect that any other code in user.conf runs as well, so be careful and don't let strangers modify that file.
Then it uses ${!var#}, which expands to the names of variables whose names begin with a prefix, here "var", or for you "USER". You could also use ${!var*}, depending on whether you want all values in one quoted variable or multiple ones. See shell parameter expansion for details.
The whole approach is tied to a common prefix for your config variables. In this case, you'll also see $USER in the output, which is the name of the currently logged in user. You can filter that with e.g., grep or a simple if [ "$i" != "USER" ] in the loop.
If you want undefined variables as well, sourcing the users file may not be a good solution. You could instead read the file line by line and check for a leading #:
#!/usr/bin/env bash
set -eu
while IFS= read -r line
do
var=$(echo "$line" | cut -d '=' -f 1)
name=$(echo "$line" | cut -d '=' -f 2)
if [[ "$var" =~ ^# ]]
then
var=$(echo "$var" | cut -c 2-)
echo "The variable $var is not set"
else
echo "The variable $var is set to $name"
fi
done
Output:
bash users.sh < users.conf
The variable USER1_USERNAME is set to "John"
The variable USER2_USERNAME is not set
The variable USER3_USERNAME is set to "David"
The variable USER4_USERNAME is set to "James"
The variable USER5_USERNAME is set to "Jenny"
However, this approach is brittle as it doesn't understand bash syntax. A leading space would be fine when sourcing, but would trip the comment detection on this code. Variables in user names would not get expanded, which may or may not be a good thing.
I liked what Robert suggested. But IMHO whole concept of your script is wrong. This should be done with array(s) not dynamic vars.
users=(
"John"
"Mike"
"David"
"James"
"Jenny"
)
And than you just loop over that array
for user in "${users[#]}"; { echo "$user"; }
If you want to make this config editable by users this can also be done, thay can comment, delete or add items in array:
users=(
"John"
# "David"
"James"
"Jenny"
"Lenny"
)
I also found my own way to deal with this by modifying the source file user.conf to have suffix at the end:
#user.conf
USER_USERNAME1="John"
#USER_USERNAME2="Mike"
USER_USERNAME3="David"
USER_USERNAME4="James"
USER_USERNAME5="Jenny"
#!/bin/bash
source "user.conf"
usernames="${!USER_USERNAME#}"
username_count=$(echo "${usernames}" | wc -w)
count=1
while [[ ${count} -le ${username_count} ]]; do
typeset -n "username"="USER_USERNAME${count}"
if [ -n "${username}" ]; then
echo "The variable USER_USERNAME${count} is set to ${username}"
else
echo "The variable USER_USERNAME${count} is not set"
fi
((count = count + 1))
done
Since it's restricted to put this in question, I post it here.

Getting wrong value of passed argument to function in bash script

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.

How to loop script till user input is empty?

I am trying to make my script to repeat till the user leaves the block question empty. I just got the loop to run, but I can not find a way to make it possible to stop it when block is empty.
I hope some one can help me!!
#!/bin/tcsh -f
#
set word="start"
until ($word !=""); do
#First ask for Compound and Block Name.
echo -n "please enter block name: "
read block
echo -n "please enter compound name: "
read compound
#Now coping template with new name
#
cp Template $block
#
for line in `cat $block`;do
echo $line | sed -e "s/test1/${block}/g" -e "s/test2/${compound}/g" >>./tmp124.txt
done
mv ./tmp124.txt $block
done
Do you want to use bash or csh? You are using bash syntax but tagged your question csh and call tcsh in the first line of your code.
To answer your question, here are examples of how to iterate on standard input until some input is empty:
For tcsh:
#!/bin/tcsh
while ( 1 )
set word = "$<"
if ( "$word" == "" ) then
break
endif
# rest of code...
end
For bash:
#!/bin/bash
while read word; do
if [ -z $word ]; then
break
fi
# rest of code...
done
Use "Until do" loop,
Eg :
For session variable i am assigning default value, Then entering loop. User can pass any value on each prompt when the value is empty, Loop will Terminate and exit the script.
session="Mysession"
until [$session -eq $null]
do
echo $session
echo "Leave Blank to Terminate session"
read -p "Enter session name : " session
done
echo "Exiting.."

Bash function parameter assignment to read a file (list) and write a file (array)

I am trying to write a bash function I can call regularly from within a larger set of scripts. I want to pass this function the name of a file containing a plain list of text strings:
blue
red
green
... and have the function write out these strings to a different file (the name of which is also passed as parameter to the function) in bash-compatible array format:
[Bb]lue [Rr]ed [Gg]reen
I can't get the function to (internally) recognise the name of the output file being passed. It throws an "ambiguous redirect" error and then a bunch of "No such file or directory" errors after that. It is however processing the input file OK. The problem appears be how I am assigning the parameter to a local string in the function. Unfortunately I have changed the loc_out= line in the function so many times that I can no longer recall all the forms I have tried. Hopefully the example is clear, if not best practise:
process_list () {
# assign input file name to local string
loc_in=(${1});
# assign output file name to local string
loc_out=($(<${2})); # this is not right
while read line
do
echo "loc_out before: $loc_out";
echo "loc_in term: $line";
item_length=${#line};
# loop until end of string
for (( i=0; i<$item_length; i++ ));
do
echo "char $i of $line: ${line:$i:1}";
# write out opening bracket and capital
if [ ${i} -eq 0 ]; then
echo -e "[" >> $loc_out;
echo -e ${line:$i:1} | tr '[:lower:]' '[:upper:]' >> "${loc_out}";
fi;
# write out current letter
echo -e ${line:$i:1} >> "${loc_out}";
# write out closing bracket
if [ ${i} -eq 0 ]; then
echo -e "]" >> "${loc_out}";
fi;
done;
# write out trailing space
echo -e " " >> "${loc_out}";
# check the output file
echo "loc_out after: ${loc_out}";
done < $loc_in;
}
f_in="/path/to/colour_list.txt";
f_out="/path/to/colour_array.txt";
echo "loc_in (outside function): ${loc_in}";
echo "loc_out (outside function): ${loc_out}";
process_list $f_in $f_out;
Any assistance on what I am doing wrong would be much appreciated.
Change:
loc_out=($(<${2})); # this is not right
To this:
loc_out=(${2}); # this should be right
You want in that line just the file name.
Hopefully this will solve your problem.
EDIT:
Besides you could/should write this:
loc_in=${1};
loc_out=${2};
You do not need parantheses, as far as I understand.

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

Resources