grep result into array - arrays

I want to count the number of IP's in a given file using this function, the IP's should go into an array so that I can use them later, but I get 'declare: not found' and 'cnt+=1: not found', why is this?
#!/bin/bash
searchString=$1
file=$2
countLines()
{
declare -a ipCount
cnt=0
while read line ; do
((cnt+=1))
ipaddr=$( echo "$line" | grep -o -E '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' )
((ipCount[$ipaddr]+=1))
done
for ip in ${ipCount[*]}
do
printf "%-15s %s\n" "$ip" "${ipCount[$ip]}"
done
echo "total count=$cnt"
}
if [ $# -lt 2 ]; then
echo "missing searchString and file"
else
grep "$searchString" $file | countLines
fi
This is a piece of the test file I am trying on
Apr 25 11:33:21 Admin CRON[2792]: pam_unix(cron:session): session opened for user 192.168.1.2 by (uid=0)
Apr 25 12:39:01 Admin CRON[2792]: pam_unix(cron:session): session closed for user 192.168.1.2
Apr 27 07:42:07 John CRON[2792]: pam_unix(cron:session): session opened for user 192.168.2.22 by (uid=0)
The desired output would be just the IP's inside an array, then also a 'count' on how many IP's there were.
I know I can get the ip's with a grep command but I would like to do more with it later, and it's important that it's in an array.

Your two main issues were that you were using declare -a but to declare an associative array, you need declare -A. Then, to iterate over the keys of an associative array, you need to use for foo in ${!ArrayName[#]}. I also added some quotes to your variables to be on the safe side:
#!/bin/bash
searchString="$1"
file="$2"
countLines()
{
## You need -A for associative arrays
declare -A ipCount
cnt=0
while read line ; do
(( cnt+=1 ))
ipaddr=$( echo "$line" | grep -o -E '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' )
(( ipCount["$ipaddr"]+=1 ))
done
## To iterate over the keys of an associative
## array, you need ${!ArrayName[#]}
for ip in "${!ipCount[#]}"
do
printf "%-15s %s\n" "$ip" "${ipCount[$ip]}"
done
echo "total count=$cnt"
}
if [ $# -lt 2 ]; then
echo "missing searchString and file"
else
grep "$searchString" "$file" | countLines
fi
This is the output of the above on your example file:
$ bash a.sh 27 a
192.168.2.22 1
192.168.1.2 2
total count=3

Related

Put every other value into a seperate array

I have a basic array at the moment and it works as expected. But what I want to do is put every other value into a seperate array. I just need the 100 and 200 to be in array 1, and 1000 and 2000 to be in array 2. How do I do that?
$ cat scriptx.sh
while read line
do
my_array=("${my_array[#]}" $line)
done
echo ${my_array[#]}
$ ./scriptx.sh
100
1000
200
2000
100 1000 200 2000
For an arbitrary number of arrays n_arrays, you can use a counter of lines read so far, dynamically generate array names, such as array_0, array_1 etc., and append new lines to the right arrays based on counter % n_arrays. Obviously, n_arrays set to 2 gives an answer to the question.
#!/bin/bash
set -euo pipefail
declare -ri n_arrays=2 # number of arrays to use
declare -i i counter=0 # number of lines read so far
for ((i = 0; i < n_arrays; ++i)); do
declare -a "array_${i}" # optional; needed with set -u
done
while IFS= read -r line; do
declare -n my_array="array_$((counter++ % n_arrays))"
my_array+=("$line") # append line to the right array
done
for ((i = 0; i < n_arrays; ++i)); do
declare -n my_array="array_${i}"
echo "${my_array[#]#A}" # print a declare+assign command
done
Something along the lines of:
#!/bin/bash
while IFS= read -r line; do
array1+=("$line")
if IFS= read -r line; then
array2+=("$line")
fi
done
echo "array1: ${array1[*]}"
echo "array2: ${array2[*]}"
You could do something like this:
#!/bin/bash
i=0
while read line; do
if (( i++ % 2 )); then
odd_indexes+=($line)
else
even_indexes+=($line)
fi
done
echo "even_indexes: ${even_indexes[*]}"
echo "odd_indexes: ${odd_indexes[*]}"
Assuming that your input data is:
100
1000
200
2000
This script will output:
even_indexes: 100 200
odd_indexes: 1000 2000
But you could also do something like this if you don't mind reading the input file twice:
#!/bin/bash
mapfile -t odd_indexes < <(sed -n 'n;p' data.txt)
mapfile -t even_indexes < <(sed -n 'p;n' data.txt)
echo "even_indexes: ${even_indexes[*]}"
echo "odd_indexes: ${odd_indexes[*]}"
If you're reading from stdin you would need to write that out to a temporary file and use that file in the above script.
One idea:
$ cat runme
#!/bin/bash
cnt=${1:-2} # 1st (optional) arg states number of arrays; if not supplied default is '2'
for ((i=1;i<=cnt;i++))
do
unset array_"$i" # make sure arrays do not exist
done
n=0
while read -r line
do
(( ndx = n++%cnt +1 )) # calculate array index
declare -n curr_array=array_"${ndx}" # use nameref to dynamically reference correct array
curr_array+=( "${line}" ) # add line to array
done
echo ""
for ((i=1;i<=cnt;i++)) # display contents of our arrays
do
typeset -p array_"$i"
done
Taking for a test drive:
$ runme
100
1000
200
2000
declare -a array_1=([0]="100" [1]="200")
declare -a array_2=([0]="1000" [1]="2000")
$ runme 2
100
1000
200
2000
declare -a array_1=([0]="100" [1]="200")
declare -a array_2=([0]="1000" [1]="2000")
$ runme 3
1 a
2 b
3 c
4 d
5 e
declare -a array_1=([0]="1 a" [1]="4 d")
declare -a array_2=([0]="2 b" [1]="5 e")
declare -a array_3=([0]="3 c")

Create an array with device logical name based on list of serial numbers

I'm tying to identify disks based on a list of UUID I get out of an api call.
The list of devices on the linux VM is following:
NAME="sda" SERIAL="NUTANIX_NFS_5_0_70509_a07b6add_60c9_4758_8b27_0afe77820dbd"
NAME="sdb" SERIAL="NUTANIX_NFS_18_0_625_ae748138_9bc0_4499_8068_d00a84a800b6"
NAME="sdc" SERIAL="NUTANIX_NFS_18_0_626_8a082956_a2be_42ca_a14b_7d21ed3d5a2d"
NAME="sdd" SERIAL="NUTANIX_NFS_18_0_627_6185353f_5c13_47ab_af58_b72d4d07106b"
NAME="sde" SERIAL="NUTANIX_NFS_18_0_628_fbeed366_6956_4de1_a217_487d75fd003c"
NAME="sdf" SERIAL="NUTANIX_NFS_18_0_629_019dcefb_0e1b_4086_96a9_982ae52fd88a"
NAME="sdg" SERIAL="NUTANIX_NFS_18_0_630_5d0b55e8_d0bd_4ee4_a2be_0bef88d94732"
NAME="sdh" SERIAL="NUTANIX_NFS_18_0_631_823beaed_1c2e_4203_ab5f_be294382d0c5"
NAME="sdi" SERIAL="NUTANIX_NFS_18_0_632_e4ee8620_5b53_4187_8f8c_7123ee8d3486"
NAME="sdj" SERIAL="NUTANIX_NFS_18_0_633_41551a1b_7dd9_4fd4_95bc_1fcdebb9dc85"
NAME="sdk" SERIAL="NUTANIX_NFS_18_0_634_12d517fc_e726_4512_b176_5d4075d0ecfe"
NAME="sdl" SERIAL="NUTANIX_NFS_18_0_635_d682eee4_cec0_4809_9efe_97aec7bf15d0"
NAME="sdm" SERIAL="NUTANIX_NFS_18_0_636_59ea679e_cbd3_4cf5_a974_67409b719391"
NAME="sdn" SERIAL="NUTANIX_NFS_18_0_637_5523c88e_310a_4fb6_b76f_48f624d1ce1f"
NAME="sdo" SERIAL="NUTANIX_NFS_18_0_638_0e189f7a_785d_46f1_bb56_8f422d421ecb"
NAME="sdp" SERIAL="NUTANIX_NFS_18_0_639_9143ba87_d742_4130_bbe0_2084e8ebad3f"
NAME="sdq" SERIAL="NUTANIX_NFS_18_0_640_3b823817_c2b6_49af_9a55_fc4706216501"
The list of drives I want to identify is in this format:
'ae748138_9bc0_4499_8068_d00a84a800b6'
'8a082956_a2be_42ca_a14b_7d21ed3d5a2d'
'6185353f_5c13_47ab_af58_b72d4d07106b'
'fbeed366_6956_4de1_a217_487d75fd003c'
'019dcefb_0e1b_4086_96a9_982ae52fd88a'
'5d0b55e8_d0bd_4ee4_a2be_0bef88d94732'
'823beaed_1c2e_4203_ab5f_be294382d0c5'
'e4ee8620_5b53_4187_8f8c_7123ee8d3486'
'41551a1b_7dd9_4fd4_95bc_1fcdebb9dc85'
'12d517fc_e726_4512_b176_5d4075d0ecfe'
'd682eee4_cec0_4809_9efe_97aec7bf15d0'
'59ea679e_cbd3_4cf5_a974_67409b719391'
'5523c88e_310a_4fb6_b76f_48f624d1ce1f'
'0e189f7a_785d_46f1_bb56_8f422d421ecb'
'9143ba87_d742_4130_bbe0_2084e8ebad3f'
'3b823817_c2b6_49af_9a55_fc4706216501'
I need to identify the logical name for only the UUIDs on that list in order to create a LVM volume group out of them. But I'm don't understand how to proceed from there. Anyone can propose an example ?
After removing the 's from you list of UUIDs (can be done with tr) you could use simply grep to extract the corresponding lines from your list of devices. To extract only the values of NAME="..." from these lines use cut.
grep -Ff <(tr -d \' < fileListOfUUIDs) fileListOfDevices | cut -d\" -f2
If the inputs you showed us are not stored in files but generated by commands you can use
commandListOfDevices | grep -Ff <(commandListOfUUIDs | tr -d \') | cut -d\" -f2
You are trying to extract field values from match results generated by a set of tokens. sed or awk can do the trick.
To extract the value of a field called NAME from one of the match results simply do this:
josh#linux ~ $ result='NAME="sda" FSTYPE="ext4" SERIAL="NUTANIX_...dbd"'
josh#linux ~ $ echo $result | sed -e 's/.*NAME="//; s/".*//;'
sda
To extract the value of a field called FSTYPE from the same match result:
josh#linux ~ $ echo $result | sed -e 's/.*FSTYPE="//; s/".*//;'
ext4
What if the order of the field changes? ..well the same code works.
josh#linux ~ $ result='FSTYPE="ext4" NAME="sda" SERIAL="NUTANIX_...dbd"'
josh#linux ~ $ echo $result | sed -e 's/.*NAME="//; s/".*//;'
sda
With this in mind, extraction of field values from all match results can then be processed in a loop. I have created a small script to automate the process and I am posting it here in the hope that it would be useful.
#!/bin/bash
#
# usage: doodle -F <file-containing-pattern(s)>
# doodle -P <pattern(s)>
#
if [ $# -ne 2 ]; then
echo 'invalid argument...'; exit;
else
pattern=
fi
if [ "$1" == "-F" ]; then
if [ ! -r $2 ]; then
echo "unable to read - $2"; exit
else
pattern=$(cat $2)
fi
elif [ "$1" == "-P" ]; then
pattern=$2
else
echo 'invalid argument...'; exit;
fi
if [ -z "$pattern" ]; then
echo 'invalid argument...'; exit;
fi
declare -a buffer
readarray buffer
if [ ${#buffer[*]} -gt 0 ]; then
for token in $pattern; do
for i in ${!buffer[*]}; do
match=$(echo ${buffer[i]} | sed -n "/$token/=");
if [ -n "$match" ]; then
##### Option 1 ##############################################################
# Uncomment the next line to print the same result that 'grep' would print.
#echo -e $(echo ${buffer[i]} | sed "s/$token/\\\\e[91m$token\\\\e[0m/");
##### Option 2 ##############################################################
# Uncomment the next line to print characters before the first [:space:]
#echo ${buffer[i]} | awk -F' ' '{ print $1 }';
##### Option 3 ##############################################################
# Uncomment the next line to email device-id to NSA
#TODO...
##### Option 4 ##############################################################
# Uncomment the next line to extract value of 'NAME' field
echo ${buffer[i]} | sed -e 's/.*NAME="//; s/".*//;'
##### Additional Option #####################################################
# Uncomment the next line (break command) to process one match per token
break;
fi
done
done
fi
unset buffer
To use this script, simply:
Copy the text in the above snippet into a file called doodle.
Open terminal and type:
josh#linux ~ $ chmod a+x doodle
To process tokens saved in a file called device-id.txt:
josh#linux ~ $ lsblk -nodeps -no name,serial -PS /dev/sd* | ./doodle -F device-ids.txt
To process tokens supplied as a single string:
josh#linux ~ $ lsblk -nodeps -no name,serial -PS /dev/sd* | ./doodle -P 'ae748138_9bc0_4499_8068_d00a84a800b6
8a082956_a2be_42ca_a14b_7d21ed3d5a2d
6185353f_5c13_47ab_af58_b72d4d07106b
...
3b823817_c2b6_49af_9a55_fc4706216501'
The script is very generic and you an can easily adapt it for use in related problems. You will find lots of tutorials on bash, sed and awk on the internet. There is also a comprehensive bash reference at gnu.org.

How to convert a subset of YAML into an indexed array of associative arrays?

I am using Bash 4.3 on linux.
I have this simple YAML-esque data file:
products:
product1:
name: "Product one"
price: 100
product2:
name: "Product two"
price: 200
myList:
- one
- two
And I need a shell function that, taking the above YAML file as input, can generate and then execute the below Bash code:
unset products product1 product2
# declare the associative arrays
declare -A product1
declare -A product2
# define the data
product1=(
[name]="Product 1"
[price]=100
)
product2=(
[name]="Product 2"
[price]=200
)
myList=(one two)
# declare the arrays which will contain the names of our associative arrays
products=(product1 product2)
Once I have this wonderful function, I will use the YAML files to automatically generate data, to be used in my custom CMS templating system like so:
{{#foreach product in products}}
<h3>{{product.name | uppercase}}</h3>
* {{product.price | money_with_currency £ GBP | without_trailing_zeros}}
{{/foreach}}
I have already tried various YAML parsers, but have not found one that can generate the associative arrays that I need, and some simply didn't work at all (for me, at least):
https://github.com/ArtBIT/bash-yaml
https://github.com/luodongseu/shyaml
https://github.com/binaryphile/y2s
https://github.com/Hashfyre/yamlparser
https://github.com/ash-shell/yaml-parse
https://github.com/jasperes/bash-yaml
https://github.com/mrbaseman/parse_yaml
https://github.com/azohra/yaml.sh
https://github.com/Minlison/yaml-parser
https://gist.github.com/pkuczynski/8665367
Most of these, as far as I understand their usage generate things like product_product1_name="foo" :(
yaml.sh, which you linked in the question, is a surprisingly good parser. It's a lot easier to convert its output into the format you need than to do anything else.
cleanupValue() {
local result
case $1 in
'"'*'"') result=${1#'"'}; result=${result%'"'} ;;
"["*"]") result=${1#'['}; result=${result%']'} ;;
*) result=$1
esac
printf '%s\n' "$result"
}
record_data() {
local key value="$(cleanupValue "$1")"; shift
while (( $# >= 2 )); do
key=$(cleanupValue "$2")
if (( $# > 2 )); then
declare -g -a "$1" || continue
declare -g -A "_${1}__seen" || continue
local -n __array="$1"
local -n __seen_array="_${1}__seen"
if ! [[ ${__seen_array[$key]} ]]; then
__seen_array[$key]=1
__array+=( "$key" )
fi
unset -n __seen_array
else
declare -g -A "$1" || continue # "continue" to skip invalid variable names
local -n __array="$1" || continue
__array[$key]=$value
fi
unset -n __array
shift
done
}
while IFS='=' read -r key value; do
IFS=. read -r -a key_pieces <<<"$key"
record_data "$value" "${key_pieces[#]}"
done < <(ysh -f your.yml)
# demonstrate results
declare -p products product1 product2 myList
...emits as output:
declare -a products=([0]="product1" [1]="product2")
declare -A product1=([price]="100" [name]="Product one" )
declare -A product2=([price]="200" [name]="Product two" )
declare -A myList=([1]="two" [0]="one" )

Fetching data into an array

I have a file like this below:
-bash-4.2$ cat a1.txt
0 10.95.187.87 5444 up 0.333333 primary 0 false 0
1 10.95.187.88 5444 up 0.333333 standby 1 true 0
2 10.95.187.89 5444 up 0.333333 standby 0 false 0
I want to fetch the data from the above file into a 2D array.
Can you please help me with a suitable way to put into an array.
Also post putting we need put a condition to check whether the value in the 4th column is UP or DOWN. If it's UP then OK, if its down then below command needs to be executed.
-bash-4.2$ pcp_attach_node -w -U pcpuser -h localhost -p 9898 0
(The value at the end is getting fetched from the 1st column.
You could try something like that:
while read -r line; do
declare -a array=( $line ) # use IFS
echo "${array[0]}"
echo "${array[1]}" # and so on
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
Or:
while read -r -a array; do
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
This works only if field are space separated (any kind of space).
You could probably mix that with regexp if you need more precise control of the format.
Firstly, I don't think you can have 2D arrays in bash. But you can however store lines into a 1-D array.
Here is a script ,parse1a.sh, to demonstrate emulation of 2D arrays for the type of data you included:
#!/bin/bash
function get_element () {
line=${ARRAY[$1]}
echo $line | awk "{print \$$(($2+1))}" #+1 since awk is one-based
}
function set_element () {
line=${ARRAY[$1]}
declare -a SUBARRAY=($line)
SUBARRAY[$(($2))]=$3
ARRAY[$1]="${SUBARRAY[#]}"
}
ARRAY=()
while IFS='' read -r line || [[ -n "$line" ]]; do
#echo $line
ARRAY+=("$line")
done < "$1"
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
for line in "${ARRAY[#]}"; do
#echo $line
if [ "$(echo $line | awk '{print $4}')" == "down" ]; then
echo "Replace this with what to do for down"
else
echo "...and any action for up - if required"
fi
done
echo "Element access of [2,3]:"
echo "get_element 2 3 : "
get_element 2 3
echo "set_element 2 3 left: "
set_element 2 3 left
echo "get_element 2 3 : "
get_element 2 3
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
It can be executed by:
./parsea1 a1.txt
Hope this is close to what you are looking for. Note that this code will loose all indenting spaces during manipulation, but a formatted update of the lines could solve that.

Pass multiple arrays as arguments to a Bash script?

I've looked, but have only seen answers to one array being passed in a script.
I want to pass multiple arrays to a bash script that assigns them as individual variables as follows:
./myScript.sh ${array1[#]} ${array2[#]} ${array3[#]}
such that: var1=array1 and var2=array2 and var3=array3
I've tried multiple options, but doing variableName=("$#") combines all arrays together into each variable. I hope to have in my bash script a variable that represents each array.
The shell passes a single argument vector (that is to say, a simple C array of strings) off to a program being run. This is an OS-level limitation: There exists no method to pass structured data between two programs (any two programs, written in any language!) in an argument list, except by encoding that structure in the contents of the members of this array of C strings.
Approach: Length Prefixes
If efficiency is a goal (both in terms of ease-of-parsing and amount of space used out of the ARG_MAX limit on command-line and environment storage), one approach to consider is prefixing each array with an argument describing its length.
By providing length arguments, however, you can indicate which sections of that argument list are supposed to be part of a given array:
./myScript \
"${#array1[#]}" "${array1[#]}" \
"${#array2[#]}" "${array2[#]}" \
"${#array3[#]}" "${array3[#]}"
...then, inside the script, you can use the length arguments to split content back into arrays:
#!/usr/bin/env bash
array1=( "${#:2:$1}" ); shift "$(( $1 + 1 ))"
array2=( "${#:2:$1}" ); shift "$(( $1 + 1 ))"
array3=( "${#:2:$1}" ); shift "$(( $1 + 1 ))"
declare -p array1 array2 array3
If run as ./myScript 3 a b c 2 X Y 1 z, this has the output:
declare -a array1='([0]="a" [1]="b" [2]="c")'
declare -a array2='([0]="X" [1]="Y")'
declare -a array3='([0]="z")'
Approach: Per-Argument Array Name Prefixes
Incidentally, a practice common in the Python world (particularly with users of the argparse library) is to allow an argument to be passed more than once to amend to a given array. In shell, this would look like:
./myScript \
"${array1[#]/#/--array1=}" \
"${array2[#]/#/--array2=}" \
"${array3[#]/#/--array3=}"
and then the code to parse it might look like:
#!/usr/bin/env bash
declare -a args array1 array2 array3
while (( $# )); do
case $1 in
--array1=*) array1+=( "${1#*=}" );;
--array2=*) array2+=( "${1#*=}" );;
--array3=*) array3+=( "${1#*=}" );;
*) args+=( "$1" );;
esac
shift
done
Thus, if your original value were array1=( one two three ) array2=( aye bee ) array3=( "hello world" ), the calling convention would be:
./myScript --array1=one --array1=two --array1=three \
--array2=aye --array2=bee \
--array3="hello world"
Approach: NUL-Delimited Streams
Another approach is to pass a filename for each array from which a NUL-delimited list of its contents can be read. One chief advantage of this approach is that the size of array contents does not count against ARG_MAX, the OS-enforced command-line length limit. Moreover, with an operating system where such is available, the below does not create real on-disk files but instead creates /dev/fd-style links to FIFOs written to by subshells writing the contents of each array.
./myScript \
<( (( ${#array1[#]} )) && printf '%s\0' "${array1[#]}") \
<( (( ${#array2[#]} )) && printf '%s\0' "${array2[#]}") \
<( (( ${#array3[#]} )) && printf '%s\0' "${array3[#]}")
...and, to read (with bash 4.4 or newer, providing mapfile -d):
#!/usr/bin/env bash
mapfile -d '' array1 <"$1"
mapfile -d '' array2 <"$2"
mapfile -d '' array3 <"$3"
...or, to support older bash releases:
#!/usr/bin/env bash
declare -a array1 array2 array3
while IFS= read -r -d '' entry; do array1+=( "$entry" ); done <"$1"
while IFS= read -r -d '' entry; do array2+=( "$entry" ); done <"$2"
while IFS= read -r -d '' entry; do array3+=( "$entry" ); done <"$3"
Charles Duffy's response works perfectly well, but I would go about it a different way that makes it simpler to initialize var1, var2 and var3 in your script:
./myScript.sh "${#array1[#]} ${#array2[#]} ${#array3[#]}" \
"${array1[#]}" "${array2[#]}" "${array3[#]}"
Then in myScript.sh
#!/bin/bash
declare -ai lens=($1);
declare -a var1=("${#:2:lens[0]}") var2=("${#:2+lens[0]:lens[1]}") var3=("${#:2+lens[0]+lens[1]:lens[2]}");
Edit: Since Charles has simplified his solution, it is probably a better and more clear solution than mine.
Here is a code sample, which shows how to pass 2 arrays to a function. There is nothing more than in previous answers except it provides a full code example.
This is coded in bash 4.4.12, i.e. after bash 4.3 which would require a different coding approach. One array contains the texts to be colorized, and the other array contains the colors to be used for each of the text elements :
function cecho_multitext () {
# usage : cecho_multitext message_array color_array
# what it does : Multiple Colored-echo.
local -n array_msgs=$1
local -n array_colors=$2
# printf '1: %q\n' "${array_msgs[#]}"
# printf '2: %q\n' "${array_colors[#]}"
local i=0
local coloredstring=""
local normalcoloredstring=""
# check array counts
# echo "msg size : "${#array_msgs[#]}
# echo "col size : "${#array_colors[#]}
[[ "${#array_msgs[#]}" -ne "${#array_colors[#]}" ]] && exit 2
# build the colored string
for msg in "${array_msgs[#]}"
do
color=${array_colors[$i]}
coloredstring="$coloredstring $color $msg "
normalcoloredstring="$normalcoloredstring $msg"
# echo -e "coloredstring ($i): $coloredstring"
i=$((i+1))
done
# DEBUG
# echo -e "colored string : $coloredstring"
# echo -e "normal color string : $normal $normalcoloredstring"
# use either echo or printf as follows :
# echo -e "$coloredstring"
printf '%b\n' "${coloredstring}"
return
}
Calling the function :
#!/bin/bash
green='\E[32m'
cyan='\E[36m'
white='\E[37m'
normal=$(tput sgr0)
declare -a text=("one" "two" "three" )
declare -a color=("$white" "$green" "$cyan")
cecho_multitext text color
Job done :-)
I do prefer using base64 to encode and decode arrays like:
encode_array(){
local array=($#)
echo -n "${array[#]}" | base64
}
decode_array(){
echo -n "$#" | base64 -d
}
some_func(){
local arr1=($(decode_array $1))
local arr2=($(decode_array $2))
local arr3=($(decode_array $3))
echo arr1 has ${#arr1[#]} items, the second item is ${arr1[2]}
echo arr2 has ${#arr2[#]} items, the third item is ${arr2[3]}
echo arr3 has ${#arr3[#]} items, the here the contents ${arr3[#]}
}
a1=(ab cd ef)
a2=(gh ij kl nm)
a3=(op ql)
some_func "$(encode_array "${a1[#]}")" "$(encode_array "${a2[#]}")" "$(encode_array "${a3[#]}")"
The output is
arr1 has 3 items, the second item is cd
arr2 has 4 items, the third item is kl
arr3 has 2 items, the here the contents op ql
Anyway, that will not work with values that have tabs or spaces. If required, we need a more elaborated solution. something like:
encode_array()
{
for item in "$#";
do
echo -n "$item" | base64
done | paste -s -d , -
}
decode_array()
{
local IFS=$'\2'
local -a arr=($(echo "$1" | tr , "\n" |
while read encoded_array_item;
do
echo "$encoded_array_item" | base64 -d;
echo "$IFS"
done))
echo "${arr[*]}";
}
test_arrays_step1()
{
local IFS=$'\2'
local -a arr1=($(decode_array $1))
local -a arr2=($(decode_array $2))
local -a arr3=($(decode_array $3))
unset IFS
echo arr1 has ${#arr1[#]} items, the second item is ${arr1[1]}
echo arr2 has ${#arr2[#]} items, the third item is ${arr2[2]}
echo arr3 has ${#arr3[#]} items, the here the contents ${arr3[#]}
}
test_arrays()
{
local a1_2="$(echo -en "c\td")";
local a1=("a b" "$a1_2" "e f");
local a2=(gh ij kl nm);
local a3=(op ql );
a1_size=${#a1[#])};
resp=$(test_arrays_step1 "$(encode_array "${a1[#]}")" "$(encode_array "${a2[#]}")" "$(encode_array "${a3[#]}")");
echo -e "$resp" | grep arr1 | grep "arr1 has $a1_size, the second item is $a1_2" || echo but it should have only $a1_size items, with the second item as $a1_2
echo "$resp"
}
Based on the answers to this question you could try the following.
Define the arrays as variable on the shell:
array1=(1 2 3)
array2=(3 4 5)
array3=(6 7 8)
Have a script like this:
arg1=("${!1}")
arg2=("${!2}")
arg3=("${!3}")
echo "arg1 array=${arg1[#]}"
echo "arg1 #elem=${#arg1[#]}"
echo "arg2 array=${arg2[#]}"
echo "arg2 #elem=${#arg2[#]}"
echo "arg3 array=${arg3[#]}"
echo "arg3 #elem=${#arg3[#]}"
And call it like this:
. ./test.sh "array1[#]" "array2[#]" "array3[#]"
Note that the script will need to be sourced (. or source) so that it is executed in the current shell environment and not a sub shell.

Resources