Shell Script that performs different functions based on input from file - linux

I am trying to merge two very different scripts together for consolidation and ease of use purposes. I have an idea of how I want these scripts to look and operate, but I could use some help getting started. Here is the flow and look of the script:
The input file would be a standard text file with this syntax:
#Vegetables
Broccoli|Green|14
Carrot|Orange|9
Tomato|Red|7
#Fruits
Apple|Red|15
Banana|Yellow|5
Grape|Purple|10
The script would take the input of this file. It would ignore the commented portions, but use them to dictate the output. So based on the fact that it is a Vegetable, it would perform a specific function with the values listed between the delimiter (|). Then it would go to the Fruits and do something different with the values, based on that delimiter. Perhaps, I would add Vegetable/Fruit to one of the values and dependent on that value it would perform the function while in this loop to read the file. Thank you for your help in getting this started.
UPDATE:
So I am trying to implement the IFS setup and thought of a more logical arrangement. The input file will have the "categories" displayed within the parameters. So the setup will be like this:
Vegetable|Carrot|Yellow
Fruit|Apple|Red
Vegetable|Tomato|Red
From there, the script will read in the lines and perform the function. So basically this type of setup in shell:
while read -r category item color
do
if [[ $category == "Vegetable" ]] ; then
echo "The $item is $color"
elif [[ $category == "Fruit" ]] ; then
echo "The $item is $color"
else
echo "Bad input"
done < "$input_file"
Something along those lines...I am just having trouble putting it all together.

Use read to input the lines. Do a case statement on their prefix:
{
while read DATA; do
case "$DATA" in
\#*) ... switch function ...;;
*) eval "$FUNCTION";;
esac
done
} <inputfile
Dependent on your problem you might want to experiment with setting $IFS before reading and read multiple variables in 1 go.

You can redefine the processing function each time you meet a # directive:
#! /bin/bash
while read line ; do
if [[ $line == '#Vegetables' ]] ; then
process () {
echo Vegetables: "$#"
}
elif [[ $line == '#Fruits' ]] ; then
process () {
echo Fruits: "$#"
}
else
process $line
fi
done < "$1"
Note that the script does not skip empty lines.

Related

Changing global var inside function doesnt mutate global variable [duplicate]

I'm working with this:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
I have a script like below:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
test1
echo "$e"
Which returns:
hello
4
But if I assign the result of the function to a variable, the global variable e is not modified:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
ret=$(test1)
echo "$ret"
echo "$e"
Returns:
hello
2
I've heard of the use of eval in this case, so I did this in test1:
eval 'e=4'
But the same result.
Could you explain me why it is not modified? How could I save the echo of the test1 function in ret and modify the global variable too?
When you use a command substitution (i.e., the $(...) construct), you are creating a subshell. Subshells inherit variables from their parent shells, but this only works one way: A subshell cannot modify the environment of its parent shell.
Your variableĀ e is set within a subshell, but not the parent shell. There are two ways to pass values from a subshell to its parent. First, you can output something to stdout, then capture it with a command substitution:
myfunc() {
echo "Hello"
}
var="$(myfunc)"
echo "$var"
The above outputs:
Hello
For a numerical value in the range of 0 through 255, you can use return to pass the number as the exit status:
mysecondfunc() {
echo "Hello"
return 4
}
var="$(mysecondfunc)"
num_var=$?
echo "$var - num is $num_var"
This outputs:
Hello - num is 4
This needs bash 4.1 if you use {fd} or local -n.
The rest should work in bash 3.x I hope. I am not completely sure due to printf %q - this might be a bash 4 feature.
Summary
Your example can be modified as follows to archive the desired effect:
# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$#" "$?"; }
_capture() { { out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$#")"; }
e=2
# Add following line, called "Annotation"
function test1_() { passback e; }
function test1() {
e=4
echo "hello"
}
# Change following line to:
capture ret test1
echo "$ret"
echo "$e"
prints as desired:
hello
4
Note that this solution:
Works for e=1000, too.
Preserves $? if you need $?
The only bad sideffects are:
It needs a modern bash.
It forks quite more often.
It needs the annotation (named after your function, with an added _)
It sacrifices file descriptor 3.
You can change it to another FD if you need that.
In _capture just replace all occurances of 3 with another (higher) number.
The following (which is quite long, sorry for that) hopefully explains, how to adpot this recipe to other scripts, too.
The problem
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
d1=$(d)
d2=$(d)
d3=$(d)
d4=$(d)
echo $x $d1 $d2 $d3 $d4
outputs
0 20171129-123521 20171129-123521 20171129-123521 20171129-123521
while the wanted output is
4 20171129-123521 20171129-123521 20171129-123521 20171129-123521
The cause of the problem
Shell variables (or generally speaking, the environment) is passed from parental processes to child processes, but not vice versa.
If you do output capturing, this usually is run in a subshell, so passing back variables is difficult.
Some even tell you, that it is impossible to fix. This is wrong, but it is a long known difficult to solve problem.
There are several ways on how to solve it best, this depends on your needs.
Here is a step by step guide on how to do it.
Passing back variables into the parental shell
There is a way to pass back variables to a parental shell. However this is a dangerous path, because this uses eval. If done improperly, you risk many evil things. But if done properly, this is perfectly safe, provided that there is no bug in bash.
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; d=$(date +%Y%m%d-%H%M%S); _passback x d; }
x=0
eval `d`
d1=$d
eval `d`
d2=$d
eval `d`
d3=$d
eval `d`
d4=$d
echo $x $d1 $d2 $d3 $d4
prints
4 20171129-124945 20171129-124945 20171129-124945 20171129-124945
Note that this works for dangerous things, too:
danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"
prints
; /bin/echo *
This is due to printf '%q', which quotes everything such, that you can re-use it in a shell context safely.
But this is a pain in the a..
This does not only look ugly, it also is much to type, so it is error prone. Just one single mistake and you are doomed, right?
Well, we are at shell level, so you can improve it. Just think about an interface you want to see, and then you can implement it.
Augment, how the shell processes things
Let's go a step back and think about some API which allows us to easily express, what we want to do.
Well, what do we want do do with the d() function?
We want to capture the output into a variable.
OK, then let's implement an API for exactly this:
# This needs a modern bash 4.3 (see "help declare" if "-n" is present,
# we get rid of it below anyway).
: capture VARIABLE command args..
capture()
{
local -n output="$1"
shift
output="$("$#")"
}
Now, instead of writing
d1=$(d)
we can write
capture d1 d
Well, this looks like we haven't changed much, as, again, the variables are not passed back from d into the parent shell, and we need to type a bit more.
However now we can throw the full power of the shell at it, as it is nicely wrapped in a function.
Think about an easy to reuse interface
A second thing is, that we want to be DRY (Don't Repeat Yourself).
So we definitively do not want to type something like
x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4
The x here is not only redundant, it's error prone to always repeate in the correct context. What if you use it 1000 times in a script and then add a variable? You definitively do not want to alter all the 1000 locations where a call to d is involved.
So leave the x away, so we can write:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }
xcapture() { local -n output="$1"; eval "$("${#:2}")"; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
outputs
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
This already looks very good. (But there still is the local -n which does not work in oder common bash 3.x)
Avoid changing d()
The last solution has some big flaws:
d() needs to be altered
It needs to use some internal details of xcapture to pass the output.
Note that this shadows (burns) one variable named output,
so we can never pass this one back.
It needs to cooperate with _passback
Can we get rid of this, too?
Of course, we can! We are in a shell, so there is everything we need to get this done.
If you look a bit closer to the call to eval you can see, that we have 100% control at this location. "Inside" the eval we are in a subshell,
so we can do everything we want without fear of doing something bad to the parental shell.
Yeah, nice, so let's add another wrapper, now directly inside the eval:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${#:2}" > >(printf "%q=%q;" "$1" "$(cat)"); _passback x; } # !DO NOT USE!
# !DO NOT USE!
xcapture() { eval "$(_xcapture "$#")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
prints
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
However, this, again, has some major drawback:
The !DO NOT USE! markers are there,
because there is a very bad race condition in this,
which you cannot see easily:
The >(printf ..) is a background job. So it might still
execute while the _passback x is running.
You can see this yourself if you add a sleep 1; before printf or _passback.
_xcapture a d; echo then outputs x or a first, respectively.
The _passback x should not be part of _xcapture,
because this makes it difficult to reuse that recipe.
Also we have some unneded fork here (the $(cat)),
but as this solution is !DO NOT USE! I took the shortest route.
However, this shows, that we can do it, without modification to d() (and without local -n)!
Please note that we not neccessarily need _xcapture at all,
as we could have written everyting right in the eval.
However doing this usually isn't very readable.
And if you come back to your script in a few years,
you probably want to be able to read it again without much trouble.
Fix the race
Now let's fix the race condition.
The trick could be to wait until printf has closed it's STDOUT, and then output x.
There are many ways to archive this:
You cannot use shell pipes, because pipes run in different processes.
One can use temporary files,
or something like a lock file or a fifo. This allows to wait for the lock or fifo,
or different channels, to output the information, and then assemble the output in some correct sequence.
Following the last path could look like (note that it does the printf last because this works better here):
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_xcapture() { { printf "%q=%q;" "$1" "$("${#:2}" 3<&-; _passback x >&3)"; } 3>&1; }
xcapture() { eval "$(_xcapture "$#")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
outputs
4 20171129-144845 20171129-144845 20171129-144845 20171129-144845
Why is this correct?
_passback x directly talks to STDOUT.
However, as STDOUT needs to be captured in the inner command,
we first "save" it into FD3 (you can use others, of course) with '3>&1'
and then reuse it with >&3.
The $("${#:2}" 3<&-; _passback x >&3) finishes after the _passback,
when the subshell closes STDOUT.
So the printf cannot happen before the _passback,
regardless how long _passback takes.
Note that the printf command is not executed before the complete
commandline is assembled, so we cannot see artefacts from printf,
independently how printf is implemented.
Hence first _passback executes, then the printf.
This resolves the race, sacrificing one fixed file descriptor 3.
You can, of course, choose another file descriptor in the case,
that FD3 is not free in your shellscript.
Please also note the 3<&- which protects FD3 to be passed to the function.
Make it more generic
_capture contains parts, which belong to d(), which is bad,
from a reusability perspective. How to solve this?
Well, do it the desparate way by introducing one more thing,
an additional function, which must return the right things,
which is named after the original function with _ attached.
This function is called after the real function, and can augment things.
This way, this can be read as some annotation, so it is very readable:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "$1" "$("${#:2}" 3<&-; "$2_" >&3)"; } 3>&1; }
capture() { eval "$(_capture "$#")"; }
d_() { _passback x; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
capture d1 d
capture d2 d
capture d3 d
capture d4 d
echo $x $d1 $d2 $d3 $d4
still prints
4 20171129-151954 20171129-151954 20171129-151954 20171129-151954
Allow access to the return-code
There is only on bit missing:
v=$(fn) sets $? to what fn returned. So you probably want this, too.
It needs some bigger tweaking, though:
# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$#" "$?"; }
_capture() { { out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$#")"; }
# Here is your function, annotated with which sideffects it has.
fails_() { passback x y; }
fails() { x=$1; y=69; echo FAIL; return 23; }
# And now the code which uses it all
x=0
y=0
capture wtf fails 42
echo $? $x $y $wtf
prints
23 42 69 FAIL
There is still a lot room for improvement
_passback() can be elmininated with passback() { set -- "$#" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
_capture() can be eliminated with capture() { eval "$({ out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }
The solution pollutes a file descriptor (here 3) by using it internally.
You need to keep that in mind if you happen to pass FDs.
Note thatbash 4.1 and above has {fd} to use some unused FD.
(Perhaps I will add a solution here when I come around.)
Note that this is why I use to put it in separate functions like _capture, because stuffing this all into one line is possible, but makes it increasingly harder to read and understand
Perhaps you want to capture STDERR of the called function, too.
Or you want to even pass in and out more than one filedescriptor
from and to variables.
I have no solution yet, however here is a way to catch more than one FD, so we can probably pass back the variables this way, too.
Also do not forget:
This must call a shell function, not an external command.
There is no easy way to pass environment variables out of external commands.
(With LD_PRELOAD= it should be possible, though!)
But this then is something completely different.
Last words
This is not the only possible solution. It is one example to a solution.
As always you have many ways to express things in the shell.
So feel free to improve and find something better.
The solution presented here is quite far from being perfect:
It was nearly not tested at all, so please forgive typos.
There is a lot of room for improvement, see above.
It uses many features from modern bash, so probably is hard to port to other shells.
And there might be some quirks I haven't thought about.
However I think it is quite easy to use:
Add just 4 lines of "library".
Add just 1 line of "annotation" for your shell function.
Sacrifices just one file descriptor temporarily.
And each step should be easy to understand even years later.
Maybe you can use a file, write to file inside function, read from file after it. I have changed e to an array. In this example blanks are used as separator when reading back the array.
#!/bin/bash
declare -a e
e[0]="first"
e[1]="secondddd"
function test1 () {
e[2]="third"
e[1]="second"
echo "${e[#]}" > /tmp/tempout
echo hi
}
ret=$(test1)
echo "$ret"
read -r -a e < /tmp/tempout
echo "${e[#]}"
echo "${e[0]}"
echo "${e[1]}"
echo "${e[2]}"
Output:
hi
first second third
first
second
third
What you are doing, you are executing test1
$(test1)
in a sub-shell( child shell ) and Child shells cannot modify anything in parent.
You can find it in bash manual
Please Check: Things results in a subshell here
I had a similar problem when I wanted to remove temporary files I had created automatically. The solution I came up with was not to use command substitution, but rather to pass the name of the variable, that should take the final result, into the function. E.g.
#!/usr/bin/env bash
# array that keeps track of tmp-files
remove_later=()
# function that manages tmp-files
new_tmp_file() {
file=$(mktemp)
remove_later+=( "$file" )
# assign value (safe form of `eval "$1=$file"`)
printf -v "$1" -- "$file"
}
# function to remove all tmp-files
remove_tmp_files() { rm -- "${remove_later[#]}"; }
# define trap to remove all tmp-files upon EXIT
trap remove_tmp_files EXIT
# generate tmp-files
new_tmp_file tmpfile1
new_tmp_file tmpfile2
So, adapting this to the OP, it would be:
#!/usr/bin/env bash
e=2
function test1() {
e=4
printf -v "$1" -- "hello"
}
test1 ret
echo "$ret"
echo "$e"
Works and has no restrictions on the "return value".
Assuming that local -n is available, the following script lets the function test1 modify a global variable:
#!/bin/bash
e=2
function test1() {
local -n var=$1
var=4
echo "hello"
}
test1 e
echo "$e"
Which gives the following output:
hello
4
I'm not sure if this works on your terminal, but I found out that if you don't provide any outputs whatsoever it gets naturally treated as a void function, and can make global variable changes.
Here's the code I used:
let ran1=$(( (1<<63)-1)/3 ))
let ran2=$(( (1<<63)-1)/5 ))
let c=0
function randomize {
c=$(( ran1+ran2 ))
ran2=$ran1
ran1=$c
c=$(( c > 0 ))
}
It's a simple randomizer for games that effectively modifies the needed variables.
It's because command substitution is performed in a subshell, so while the subshell inherits the variables, changes to them are lost when the subshell ends.
Reference:
Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment
A solution to this problem, without having to introduce complex functions and heavily modify the original one, is to store the value in a temporary file and read / write it when needed.
This approach helped me greatly when I had to mock a bash function called multiple times in a bats test case.
For example, you could have:
# Usage read_value path_to_tmp_file
function read_value {
cat "${1}"
}
# Usage: set_value path_to_tmp_file the_value
function set_value {
echo "${2}" > "${1}"
}
#----
# Original code:
function test1() {
e=4
set_value "${tmp_file}" "${e}"
echo "hello"
}
# Create the temp file
# Note that tmp_file is available in test1 as well
tmp_file=$(mktemp)
# Your logic
e=2
# Store the value
set_value "${tmp_file}" "${e}"
# Run test1
test1
# Read the value modified by test1
e=$(read_value "${tmp_file}")
echo "$e"
The drawback is that you might need multiple temp files for different variables. And also you might need to issue a sync command to persist the contents on the disk between one write and read operations.
You can always use an alias:
alias next='printf "blah_%02d" $count;count=$((count+1))'

How to iterate over a series of numbers passed as arguments to a bash function and then return a count of even numbers?

#this function checks if a number is even
#checks only 1 argument
function isiteven {
if [[ $1%2 -eq 0 ]]
then
echo "1"
else
echo "0"
fi
}
I need to use this file as source to another file and create a function that returns the count of even numbers passed to a function called 'nevens'.
I tried this code:
source program6.sh
function nevens {
for check in $#
do
if [[ -e isiteven$# ]]
then
let count=count+1
fi
done
echo $count
}
I am confused what shell sign to use to iterate in for loop and to check in if statement.
Since you're outputting a 0 or a 1 from isiteven, you can just unconditionally add the output of the function:
#!/bin/bash
source program6.sh
function nevens {
local count=0
for check; do # implicit `for check in "$#"`
(( count += $(isiteven $check) )) # add output of `isiteven $check`
done
echo $count
}
This assumes that your input is all valid, so if that's not guaranteed to be the case, you will need to add some checks.
Note that this script uses several non-standard features that won't work in all shells:
source program6.sh instead of the standard . program6.sh
function keyword instead of just nevens () {
local keyword to declare a local variable inside the function
+=, instead of the standard count=$(( count + $(isiteven $check) ))

Expanding a string with a variable reference later, after the variable is assigned

I'm trying to combine two lists containing names (if available) and emails with a standard email text in bash (shell)
(I had to delete the irrelevant code as it contains some private info, so some of the code might look unusal.)
The first half of the code checks if there is a name list along with the email list.
The second half combines only the email address and text if no name is available, if the name list is available it also 'tries' to combine the name, email and text.
f1 = email list and f2 = name list.
As you can see in the first half of the code below, $f2 should show the names if the list is available but it does not show anything in the log file.
I been trying to sort this problem out for two days but nothing has worked. When names are available it always outputs as "Hello ..." when it should be "Hello John D..."
#FIRST HALF
if [ "$names" = "no" ]
then
text="Hello..."
elif [ "$names" = "yes" ]
then
text="Hello $f2..."
fi
#SECOND HALF
if [ "$names" = "no" ]
then
for i in $(cat $emaillist); do
echo "$text" >> /root/log
echo "$i" >> /root/log
done
elif [ "$names" = "yes" ]
then
paste $emaillist $namelist | while IFS="$(printf '\t')" read -r f1 f2
do
echo "$text" >> /root/log
echo "$f1" >> /root/log
done
fi
When you run text="Hello $f2", $f2 is looked up at the time of the assignment; an exact string is assigned to text, and only that exact string is used later, on echo "$text".
This is very desirable behavior: If shell variables' values could run arbitrary code, it would be impossible to write shell scripts that handled untrusted data safely... but it does mean that implementing your program requires some changes.
If you want to defer evaluation (looking up the value of $f2 at expansion time rather than assignment), don't use a shell variable at all: Use a function instead.
case $names in
yes) write_greeting() { echo "Hello $name..."; };;
*) write_greeting() { echo "Hello..."; };;
esac
while read -r name <&3 && read -r email <&4; do
write_greeting
echo "$email"
done 3<"$namelist" 4<"$emaillist" >>/root/log
Some enhancements in the code above:
You don't need paste to read from two streams in lockstep; you can simply open them on different file descriptors (above, FDs 3 and 4 are chosen; only 0, 1 and 2 are reserved, so larger numbers could have been selected as well) with a separate read command for each.
Opening your output sink only once for the entire loop (by putting the redirection after the done) is far more efficient than re-opening it every time you want to write a single line.
Expansions, such as "$namelist" and "$emaillist", are always quoted; this makes code more reliable if dealing with filenames with unusual characters (including spaces and glob expressions), or if IFS is at a non-default value.

bash script function scope

function generateFileList {
for entry in "$ORIGINATION_PATH"/*
do
entry=${entry%.*} # retain the part before the dot
entry=${entry##*/} # retain the part after the last slash
if [ $(contains "${FILENAME[#]}" $entry) == "n" ]; then
FILENAME[$fn_counter]=$entry
fn_counter=(expr $fn_counter + 1)
echo $entry "added to filelist"
echo ${FILENAME[$fn_counter]}
fi
done
NUMBER_OF_FILES=$(expr ${#FILENAME[#]} + 1)}
I have this function .My $ORIGINATION_PATH has many files in it. However, when I call this function my $FILENAME array gets populated only with one entry.Why? Inside the function everything seems fine, and it seems that $FILENAME array gets all the values it needs to get, but when I check outside the function I only get one value in the $FILENAME aray
Problems with your code and suggestions for improvement:
You should initialize ${FILENAME[#]} to an empty array (either in the function itself if you always want the function to generate a new list of files from scratch, or before calling the function if you want to be able to build up a composite list of files by calling the function repeatedly on different base directories).
You should initialize $fn_counter to zero before starting the loop. Or, for the composite build-up idea, to the number of elements currently in ${FILENAME[#]}. Actually, another, perhaps preferable solution, would be to remove the $fn_counter variable entirely and replace it with ${#FILENAME[#]}, since it should always be equal to that value.
In the line fn_counter=(expr $fn_counter + 1), you're assigning $fn_counter to an array, rather than incrementing it. This is because you forgot the dollar before the open parenthesis. If you ran fn_counter=$(expr $fn_counter + 1) then it would work. But there's a better way to increment a numeric variable: let ++fn_counter.
You don't have to dollar-prefix variables in arithmetic expressions. So, for example, we can say ${FILENAME[fn_counter]} instead of ${FILENAME[$fn_counter]}.
You're trying to echo the element of ${FILENAME[#]} that was just added in the current iteration, but indexing it with $fn_counter after it was incremented, which is incorrect. You can solve this by subtracting 1 from it, i.e. echo "${FILENAME[fn_counter-1]}". Or, if removing $fn_counter, echo "${FILENAME[${#FILENAME[#]}-1]}".
When assigning $NUMBER_OF_FILES, I don't know why you're adding 1 to ${#FILENAME[#]}. The number of elements in the ${FILENAME[#]} array should be equal to the number of files, without requiring an increment, no? I recommend removing this variable entirely, since the value can be accessed directly as ${#FILENAME[#]}.
I recommend you pass inputs as arguments (e.g. pass $ORIGINATION_PATH as an argument) and use the local keyword to reduce the likelihood of variable clashes between functions. Globals are the default in bash, which creates dangerous possibilities for different functions to step on each others' toes. For example, imagine if the contains function (assuming it's a shell function) assigned a value to the global $entry variable.
I recommend always using the [[ command rather than [, as it's more powerful, and it's good to be consistent.
As written, your script won't work correctly on an empty directory. You could test in advance if the directory is empty (e.g. [[ -n "$(find "$ORIGINATION_PATH" -maxdepth 0 -empty)" ]]). Another solution is to set nullglob. Another solution is to skip glob words that don't actually exist (e.g. if [[ ! -e "$entry" ]]; then continue; fi;).
Always double-quote variable expansions to protect against word splitting, which takes place after variable expansion. For example, the contains call should be contains "${FILENAME[#]}" "$entry" (notice the double-quoting around $entry). The only exceptions are (1) when assigning a string variable to a string variable, i.e. new=$old, in which case you don't have to quote it, and (2) when expanding a numeric variable, which is guaranteed not to be corrupted by word splitting.
Here's a working solution, filling in the missing pieces:
function contains {
local target="${#:$#:1}";
local -a array=("${#:1:$#-1}");
local elem='';
for elem in "${array[#]}"; do
if [[ "$elem" == "$target" ]]; then
echo 'y';
return;
fi;
done;
echo 'n';
} ## end contains()
function generateFileList {
local path="$1";
local entry='';
for entry in "$path"/*; do
if [[ ! -e "$entry" ]]; then continue; fi;
entry=${entry%.*}; ## retain the part before the dot
entry=${entry##*/}; ## retain the part after the last slash
if [[ "$(contains "${FILENAME[#]}" "$entry")" == 'n' ]]; then
FILENAME[${#FILENAME[#]}]=$entry;
echo "$entry added to filelist";
echo "${FILENAME[${#FILENAME[#]}-1]}";
fi;
done;
} ## end generateFileList()
ORIGINATION_PATH='...';
FILENAME=(); ## build up result on global ${FILENAME[#]} var
generateFileList "$ORIGINATION_PATH";
echo "\${#FILENAME[#]} == ${#FILENAME[#]}";
echo "\${FILENAME[#]} == (${FILENAME[#]})";

Shell Scripting - How to mock some results based on an input?

I have a small scripts which verifies some conditions on a database server. I want to mock failures on all of those conditions to test the script, so I added the following line:
./print_results ${VAR1} ${VAR2} ... ${VARN}
If any of the variables has a value different than ZERO it because it failed.
so just for testing purpouses I added the line:
VAR1=1 ; VAR2=1 ; ... ; VARN=1
But I need to edit the file every time I want to replace the real results with the fake ones.
What's wrong with this?
[! -z $1 ] && [ "$1" == "Y"] && { echo "Debugging is ACTIVE" ; VAR1=1 ; ... ; VAR2=1 ; }
I want to have the VAR1..N = 1 after passing that line.
Thanks.
The problem is that [ is a command, but [! is not. It is probably cleaner to write your code:
test "{$1}" == Y && { echo "Debugging is ACTIVE"; VAR1=1 VAR2=1 ...; }
No need for semi-colons between the variable assignments, but they don't hurt.
This is one of the warts of sh. For some reason, it was thought to be a good idea to use the symbol [ for a command and pass it ] as an argument, trying to mimic braces in the language. Unfortunately, this leads to a great deal of confusion similar to that demonstrated in this question. It is far better to avoid [ completely and always spell it test. These two are functionally identical (except that the [ command must have ] as the final argument), and using test is much cleaner. (Would you expect test! to work?, or would you recognize that it needs to be written as ! test?)
Need a space between the "Y" and the ]. The non-zero test is pointless, but also requires a space between the [ and the !.
[ "$1" == "Y" ] && { echo "Debugging is ACTIVE" ; VAR1=1 ; ... ; VAR2=1 ; }
Also did you consider just writing this as an if...fi block?
bash provides a way to supply default values for parameters that aren't otherwise set. Presumably, your code has lines like
VAR1=$1
VAR2=$2
VAR3=$3
Replace them with
VAR1=${1-1}
VAR2=${2-1}
VAR3=${3-1}
If $1 is unset, for instance, VAR1 will be assigned the value of 1 instead of the value of $1.

Resources