Bash merge specific file when I call a function - arrays

I want to merge multiple file that I create on function call, because I call three time the function on Main
aa_file
aa
bb_file
bb
cc_file
cc
Final output
final_file
aa
bb
cc
function cal () {
# Some operation
while true; do
read -p "would you like asignment ? on ${var} " yn
case $yn in
[Yy]* ) arr_var+=("$var"); echo "$var" > $var_file;
[Nn]* ) break;;
* ) echo "Please answer yes or no.";;
esac
# Some operation
done
}
# Main
eval arr_var=()
cal "aa"
cal "bb"
cal "cc"
How make a function that can merge all file
for i in "${arr_var[#]}"
do
cat $i_file $(i+1)_file > final_file
done
because, when I call only two times the function cal, that I want
# Main
eval arr_var=()
cal "aa"
cal "cc"
final_file
aa
cc

Please correct your question, because it is hard to find out what you want.
However, you can call a function multiple times, and you can merge its output into another file. Like this:
for f in `seq 1 5`
do
cat ${f}
done > output_file
Or even like this:
(
cal "aa"
cal "bb"
cal "cc"
) > output_file
In your version, the final_file gets overwritten in every iteration:
for i in "${arr_var[#]}"
do
cat $i_file $(i+1)_file > final_file
done
Either change > to >> or move the > outside the loop.

if you use bash, you can do:
arr_var=(aa bb cc)
cat "${arr_var[#]/%/_file}" > final_file

Related

Bash Global Array

I want to have a global array, each time I call the function cal on Main, I can add a new element on array arr_var
function cal () {
# Some operation
while true; do
read -p "would you like asignment ? on ${var} " yn
case $yn in
[Yy]* ) arr_var+=$var
[Nn]* ) break;;
* ) echo "Please answer yes or no.";;
esac
# Some operation
done
}
# Main
eval arr_var=()
cal "aa"
cal "bb"
cal "cc"
That I want
printf '%s\n' "${arr_var[#]}"
aa bb cc
but I get
aabbcc
Don't use eval for that...
arr_var=()
or
declare -ag arr_var
eval is evil : Why should eval be avoided in Bash, and what should I use instead?
And you have you answer in the comments :
var=()
var+=(a)
var+=(b)
declare -p var

How to store variables from loop to a file

I am trying to store the variables $d, $tf_name, $db_orig created in the following loop to a file.I want to end up with a tab separated MY_FILE.txt containing the following fields $d, $tf_name, $db_orig and each iteration of this set of variables to be stored in a new line in the file MY_FILE.txt.
MY_ARRAY=()
for d in */
do
IN=$d
folderIN=(${IN//_/ })
tf_name=${folderIN[-1]%/*}
db_orig=${folderIN[-2]%/*};
ENTRY="$d\t$tf\t$id\t$db_orig\n"
MY_ARRAY+=$ENTRY
done
$MY_ARRAY > MY_FILE.txt
It doesn't recognise \t and \n as TAB and NEWLINE respectively. It stores all the values next to each other in the same line without TAB, in the array MY_ARRAY.
Any help?
Yes, this happens because $MY_ARRAY > MY_FILE.txt is not a valid command.
You need to print your array to the file.
And in order to print it correctly you need either to use
echo -e "${MY_ARRAY[#]}" >file or printf
By man echo
echo -e : enable interpretation of backslash escapes
Moreover, if you need to store the $ENTRY to your array you need to do it like this:
MY_ARRAY+=("$ENTRY")
In any case, you can do it without the need of an array. You can just apply += in the ENTRY : ENTRY+="$d\t$tf\t$id\t$db_orig\n"
Test:
$ e+="a\tb\tc\td\n"
$ e+="aa\tbb\tcc\tdd\n"
$ e+="aaa\tbbb\tccc\tddd\n"
$ echo -e "$e"
a b c d
aa bb cc dd
aaa bbb ccc ddd
# Test with array
$ e="a\tb\tc\td\n" && myar+=("$e")
$ e="aa\tbb\tcc\tdd\n" && myar+=("$e")
$ e="aaa\tbbb\tccc\tddd\n" && myar+=("$e")
$ echo -e "${myar[#]}"
a b c d
aa bb cc dd
aaa bbb ccc ddd
#Alternative array printing
$ for i in "${myar[#]}";do echo -en "$i";done
a b c d
aa bb cc dd

Create array from many variables

Using bash I am trying to create an echo command with a list of variables. The number of variables can differ on what I am trying to do but I know how many I have as I count them in $ctr1. An example of what it should look like is:
echo "$var1,$var2,$var3"
etc. with the last variable the same number as the counter.
Can someone give me an idea as to what I should be doing, an example would be great. I know it could be done with if statements with a line for the possible number in the counter but that is not practical as there can be from 1 to 50+ variables in a line. I do not know if this should be an array or such like nor how to put this together. Any assistance on this would be a help.
Yes, this should be an array instead.
Instead of doing e.g.
var1=foo
var2=bar
var3=quux
ctr1=3
echo "${var1},${var2},${var3}"
you could do
var=("foo" "bar" "quux")
( IFS=,; echo "${var[*]}" )
Example:
$ cat test.sh
#!/bin/bash
# the following is equivalent to doing
# ( IFS=,; echo "$*" )
# but that wouldn't be a good example, would it?
for argument in "$#"; do
var+=( "${argument}" )
done
( IFS=,; echo "${var[*]}" )
.
$ ./test.sh foo
foo
$ ./test.sh foo bar
foo,bar
$ ./test.sh foo bar "qu ux"
foo,bar,qu ux

How to allocate array which is passed to a function by name

I want to create a function which iterates through an array and inserts the given value, if its not yet included. I am using this function on two different parts of my code and so I have to use different arrays. Therefore I am delivering the array names to the function. Now I can't figure out how to allocate a slot of the array to store the element at this place.
Here is my code:
name=("hello" "world")
function f {
array_name=$2[#]
array=("${!array_name}")
length=${#array_name[#]}
for (( i = 0; i <= $length; i++ )); do
if [[ "${array[i]}" = "$1" ]];
break;
fi
if [[ "$i" = "$length" ]]; then
${2[$length+1]=$1};
fi
done
}
f "test" "name"
Edit: I want the array to append the given value so something like this
for i in ${name[#]}
do
echo $i
done
would have this output:
hello
world
test
but apparently "${2[$length+1]=$1}" is not working.
(Idea for passing the array is from here: bash how to pass array as an argument to a function)
If I understand correctly you want to append a value to an array if this value is not yet in the array, but the tricky part is that the array name is passed to the function.
Method 1
One possibility is to see the problem from a different viewpoint: you can pass to the function the value to be inserted and the full array, and the function will set a global variable that you will recover after its execution. For our test example we'll use:
array=( hello world "how are you today?" )
and we'll try to insert test and hello (so that hello will not be inserted):
f() {
# This function will set the global variable _f_out to an array obtained from
# the positional parameters starting from position 2, with $1 appended if and
# only if $1 doesn't appear in the subsequent positional parameters
local v=$1 i
shift
_f_out=( "$#" )
for i; do [[ $i = $v ]] && return; done
_f_out+=( "$v" )
}
Let's use it:
$ array=( hello world "how are you today?" )
$ f test "${array[#]}"
$ array=( "${_f_out[#]}" )
$ printf '%s\n' "${array[#]}"
hello
world
how are you today?
test
$ f hello "${array[#]}"
$ array=( "${_f_out[#]}" )
$ printf '%s\n' "${array[#]}"
hello
world
how are you today?
test
It works.
Remark. I used for i; do. It's a nice shortcut for for i in "$#"; do.
Method 2
You really want to fiddle with simili-pointers and do the appending in place (this is not really in the spirit of Bash—that's why it's a bit clumsy). The tool for that is to use printf with the -v option: from help printf:
-v var assign the output to shell variable VAR rather than
display it on the standard output
The good thing is that it also works with array fields.
Warning: you might see other methods that use eval. Avoid them like the plague!
f() {
local array_name=$2[#] i
local array=( "${!array_name}" )
for i in "${array[#]}"; do [[ $i = $1 ]] && return; done
# $1 was not found in array, so let's append it
printf -v "$2[${#array[#]}]" '%s' "$1"
}
and let's try it:
$ array=( hello world "how are you today?" )
$ f test array
$ printf '%s\n' "${array[#]}"
hello
world
how are you today?
test
$ f hello array
$ printf '%s\n' "${array[#]}"
hello
world
how are you today?
test
It works too.
Note. With both methods you can very easily have a return code of the function, e.g., 0 (success) if the value was inserted, and 1 (failure) if the value was already there (or the other way round)—the adaptation is straightforward and left as an exercise. This might be useful in Method 1 to determine whether you need to update array to the returned value _f_out or not. In that case, you could even slightly modify f so that it doesn't even set _f_out when the value is already in the array.
Caveat. In both methods shown I assumed that your arrays have contiguous indices that start at 0 (i.e., non-sparse arrays). I think it's a safe assumption here; but if it is not the case, these methods are broken: the first one will (after reassignment) transform the array into a non-sparse one, and the second one might overwrite fields (as, for an array a, ${#a[#]} expands to the number of elements in the array, not the highest index + 1 found in the array).

shell script function and for loop

the following function in shell script only loops through the first element of the array when called on an array, what's wrong?
#!/bin/sh
in_array(){
a=$2
echo ${#a[#]}
for (( i = 0; i < ${#a[#]} ; i++ ))
do
echo ${a[$i]}
if [ $1 = ${a[$i]} ]
then
return 1
fi
done
return 0
}
exclude_dirs=( aaa bbb )
echo ${#exclude_dirs[#]}
in_array home $exclude_dirs
There are 2 problems. The first is that sh does not support arrays, so your shebang should be changed to a shell that does. (eg, #!/bin/bash). The second problem is more substantial. Arrays are not first class objects in bash (they may be in other shells but I'm going to answer the question for bash, since sh is often bash in many Linux distros and I'm using my crystal ball to determine that you meant bash when you said #!/bin/sh). You can get what you want by using eval. Change your function to something like this:
in_array(){
a=$2
eval "for i in \${$a[#]}; do
echo \$i
test $1 = \$i && return 1
done"
return 0
}
and invoke it without a '$'.
in_array home exclude_dirs
Also, I would strongly recommend inverting the return values. (return 0 if $1 appears in the array, and return 1 if it does not). Either that, or change the function name to "not_it_array". That will allow you to write things like:
if in_array home exclude_dirs; then echo home is excluded; fi
(or use a short circuiting &&). Remember that in sh, 0 is success, and non-zero is failure.
Of course, it would be easier to pass the array by passing all of the values rather than passing the name:
#!/bin/bash
in_array(){
el=$1
shift
while test $# -gt 0; do
test $el = $1 && return 0
shift
done
return 1
}
exclude_dirs=( aaa home bbb )
in_array home ${exclude_dirs[#]} && echo home is excluded
William is right that you can't use arrays in Bourne Shell, but you shouldn't be using eval either. To avoid it you could just simplify your parameter structure:
#!/bin/sh
in_array(){
search_path="$1"
shift
while [ -n "${1+defined}" ]
do
if [ "$1" = "$search_path" ]
then
return 0
fi
shift
done
return 1
}
in_array foo aaa bbb && echo fail test 1
in_array foo foo bar || echo fail test 2
in_array foo bar foo || echo fail test 3
in_array foo foobar && echo fail test 4
in_array foo barfoo && echo fail test 5
in_array foo "foo bar" && echo fail test 6
in_array "foo bar" "foo bar" "baz ban" || echo fail test 7
true

Resources