user3541631
user3541631

Reputation: 4008

Passing an array to a function as one argument and other arguments after it

I want to pass an array to a function and the loop thru it.

is_node ${nodes[@]} 

if I try to loop

function is_node(){
    for role in "${1[@]}"
               do

I get the following error:

bad substitution

If I first try to check the number of arguments, I notice there are more than one.

function is_node(){
   if [[ $# -ne 1 ]] then   
        echo "Error - The number of arguments is not correct. 1 argument(a role name) needed" 

I want to pass the array, just as one argument, and pass other arguments after

is_node array status limit

then inside the function loop thru it.

Upvotes: 1

Views: 104

Answers (4)

Eric Maasdorp
Eric Maasdorp

Reputation: 19

Nice Szczerba!

Your solution works perfectly, the contents of the array can change without changing the relative position of other variables, and that sets your answer appart. Here is an example backup script that can handle different sub directory's based on your solution.

#!/bin/bash

#Logging                                                                                                                     
logpath="/tmp/ram1"                                                                                                          
eDate=$( date '+%Y%m%d_%H%M%S' )                                                                                             
ext="log"                                                                                                                    
                                                                                                                             
#Backup Source/Destination drives and folder                                                                                 
                                                                                                                             
src1="/mymedia"                                                                                                              
subs1=(video audio)                                                                                                          
lbl1="M_ResQ1"                                                                                                               
dest1="/mnt/media/SG_ResQ1/Archive"                                                                                          
                                                                                                                             
src2="/mymedia"                                                                                                              
subs2=(TVSeries _In Test pic Theater)                                                                                           
lbl2="M_ResQ2"                                                                                                               
dest2="/mnt/media/SG_ResQ2/Archive"                                                                                          
                                                                                                                             
opt="-va --partial --del"                                                                                                    
#-q  quite                                                                                                                   
#-n dry run                                                                                                                  
#-P is = --partial --progress                                                                                                
                                                                                                                             
Arc (){ # $1 subs $2 from $3 lbl $4 dest                                                                                     
   declare -a subs=("${!1}")                                                                                                 
   from="$2"                                                                                                                 
   lbl=$3                                                                                                                    
   dest=$4                                                                                                                   
                                                                                                                             
   if [ -d "$dest" ]; then                                                                                                   
      for i in "${subs[@]}"; do                                                                                              
         #logto=${logpath}/${eDate}_${lbl}_${i}.${ext}                                                                       
         logto=${logpath}/${eDate}_${lbl}.${ext}                                                                             
         echo $logto $lbl $dest                                                                                              
         echo -e "\n\nStarting:\n\t${i}\tinto\t${lbl}\n\t${eDate}\n\t${opt}\n\n" | tee -a ${logto}                           
         rsync ${opt} ${from}/${i}  ${dest}/ | tee -a ${logto}                                                               
      done                                                                                                                   
      echo $( date '+Done %Y%m%d_%H%M%S' ) | tee -a ${logto}                                                                 
      cp ${logto} ${dest}/                                                                                                   
   else                                                                                                                      
      echo -e "Not mounted or wrong drive"                                                                                   
   fi                                                                                                                        
}                                                                                                                            
                                                                                                                             
Arc "subs1[@]" $src1 $lbl1 $dest1                                                                                            
Arc "subs2[@]" $src2 $lbl2 $dest2 

Upvotes: 0

Inian
Inian

Reputation: 85550

The question is perfectly valid and don't think its a duplicate of Passing arrays as parameters in bash.

The problem with passing the array as argument to the function as "${nodes[@]}" or ${nodes[@]} in this case would be at the receiving side, the array contents are not kept intact, because the contents of the array is expanded before the function is called. So when the arguments are unpacked at the receiver, they are split at $1, $2 till the size of the array. You could see it from this simple example,

set -x
newf() { echo "$1"; echo "$2"; echo "$3"; }
arr=(1 2 3)
newf "${arr[@]}"
+ newf 1 2 3
+ echo 1
1
+ echo 2
2
+ echo 3
3

as you can see the array arr is expanded to the list of positional arguments while the intention was to use an array.

So given this problem and with your claim that you have additional argument flags after the array, you need to identify in the receiver side, how to start processing arguments after the array. The best way would be to pass the array expansion using *, so that the elements quoted as a whole.

So assuming your function expects 3 arguments to it, you can define it as below. The read command on the receiver will split the whole string of array content to individual elements and store it in the array arrayArgs and you can parse it as you wish.

is_node(){
    (( $# < 3 )) && { printf 'insufficient args provided' >&2; return 1; }
    read -ra arrayArgs <<<"$1"
    printf 'Printing the array content \n'
    for element in "${arrayArgs[@]}"; do
        printf '%s\n' "$element"
    done
    printf '2nd arg=%s 3rd arg=%s\n' "$2" "$3"       
}

and pass the array as

list=(1 2 3)
is_node "${list[*]}" 4 5

Upvotes: 1

tripleee
tripleee

Reputation: 189317

You can pass in a list of arguments any way you like. The arguments to the function are simply "$@".

is_node(){
    for role in "$@"; do
        : something with "$role"
    done
}

is_node "${nodes[@]}"

Notice also the proper use of quoting, and the omission of the (gratuitous, here) Bash-only keyword function.

More tangentially, the shell assumes in "$@" if you don't pass an explicit list of tokens, so this can (slightly obscurely) be simplified to for role; do

If you have a fixed number of other arguments, just put them before the variable-length list of arguments.

Upvotes: 0

Szczerba
Szczerba

Reputation: 273

I assume that you want to write function with both arguments - array and traditional "single" ones. If I am mistaken please let me know.

My solution:

#!/bin/bash

function_with_array_and_single_argument () {
    declare -a _array1=("${!1}")
    echo "${_array1[@]}"
    echo $2 
}

array="a
b
c"

function_with_array_and_single_argument "array[@]" "Szczerba"

Output:

$ ./script.sh
a
b
c
Szczerba

Upvotes: 1

Related Questions