0

Is there a way to do the follow below?

#!/bin/bash -x

IPFILE_LIST=(
  /copytest/test1
  /copytest/test2/test.conf
  /copytest/test3/test3/test3
  /copytest/test4/test4
)

CopyFunction() {
  for i in "${$1[@]}"; do
    rsync -R $2 $3
  done
}

CopyFunction 'IPFILE_LIST' $i copytestdest

Where the function would look like this in the end

CopyFunction() {
   for i in "${IPFILE_LIST[@]}"; do
      rsync -R $i /copytestdest/
   done
}

And it would execute each item in the array for rsync, in the end i should get an output of the following

copytestdest/copytest/test1 copytest/test2/test.conf copytestdest/copytest/test3/test3/test3 copytestdest/copytest/test4/test4

I would also like to support the follow in the same fuction if possible otherwise it will likely need to be another fuction

CopyFunction copytestdest 'IPFILE_LIST'

bc81
  • 177
  • 2
  • 10
  • That's asking "how can I use an array as a positional parameter in a function", right? – Benjamin W. May 11 '17 at 16:23
  • 1
    My preferred answer is the one recommending using namerefs (`local -n`), but that requires Bash 4.3 or newer. See also the [Bash hackers wiki](http://wiki.bash-hackers.org/commands/builtin/declare#nameref) for a usage example. – Benjamin W. May 11 '17 at 16:31
  • No I don't think what i want is passing arrays as a parameter, i want to loop through the array and run the rsync command – bc81 May 11 '17 at 17:30
  • It certainly looks like you're passing the array as parameter in `CopyFunction IPFILE_LIST`. `local -n` would enable you to do exactly what you're trying to do. – Benjamin W. May 11 '17 at 17:33
  • It does but thats not what i want, I want the text 'IPFILE_LIST' to be passed then used in $1 – bc81 May 11 '17 at 17:45
  • So you have an array of paths, and you want a function that iterates over the array and executes `rsync -R array_element /copytestdest/` for each element, right? What's the purpose of having `$i` as a function parameter? – Benjamin W. May 11 '17 at 17:48
  • I want to pass '$i' as well so i can reuse the function to do something like `rsync -R /copytestdest/ array_element` – bc81 May 11 '17 at 17:50
  • See Charles' answer. I still think you want to pass the array as a parameter. – Benjamin W. May 11 '17 at 17:52
  • Huh? The variable name is baked into the function in your example, so it's **completely** nonobvious how passing that name as an argument adds any value whatsoever. – Charles Duffy May 11 '17 at 17:55
  • If what you want is a generic metafunction -- ie. a function `for_each_array_element` you could invoke as `for_each_array_element array_name function_name function_arg1 function_arg2 ...` which will call the function named `function_name` for each element in the array named `array_name` that's doable, but your question doesn't indicate anything of the sort. – Charles Duffy May 11 '17 at 17:57
  • See http://stackoverflow.com/questions/43926293/how-can-i-dynamically-substitute-array-entries-for-an-arbitrary-command-paramete/43926294#43926294 -- inspired by the question here, showing how to substitute an arrays in multiple positions. – Charles Duffy May 11 '17 at 22:03

1 Answers1

2

In bash 4.3 or newer:

CopyFunction() {
  local -n arr=$1
  local i
  for i in "${arr[@]}"; do
    rsync -R "$i" "$2"
  done
}

Without modern bash, you need to turn to hackery with eval:

CopyFunction() {
  local -a arr
  local eval_cmd i

  printf -v eval_cmd 'arr=( "${%q[@]}" )' "$1"
  eval "$eval_cmd"

  for i in "${arr[@]}"; do
    rsync -R "$i" "$2"
  done
}

With either of these, the function can be called as:

CopyFunction 'IPFILE_LIST' copytestdest

Note that in both cases best practice has the variable i declared as local. Because it's local, its value doesn't escape the function call, so there aren't any side effects to its use -- it's no longer defined after the function exits. Because it's side-effect-free, there's no point whatsoever to controlling its name from outside the function, and thus no point to passing that name in from outside.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • is there a way to use the same function for reverse? where i want the command to be `CopyFunction copytestdest 'IPFILE_LIST'` Or would that need to be a separate function? – bc81 May 11 '17 at 18:05
  • If all you want to do is swap `$1` and `$2`, then... just swap `$1` and `$2`. – Charles Duffy May 11 '17 at 18:06
  • i want to swap "$i" and "$2" – bc81 May 11 '17 at 18:29
  • Waitaminute. You're saying you want to detect whether a parameter is a valid variable name, and iterate either over source or destination based on the outcome of that determination? That really should be a separate question. (Yes, it's possible, but I'd also describe it as **definitely** a bad idea). – Charles Duffy May 11 '17 at 18:29
  • Hmm. For rsync specifically, if your function is intended to be used only with remote destinations, I suppose your non-variable argument will always contain a `:` or a `/` (neither of which is allowed variable names), so you can just iterate over whichever one does not (and if neither one does, throw an error). – Charles Duffy May 11 '17 at 18:31
  • `CopyFunction 'IPFILE_LIST' copytestdest` is one work flow `CopyFunction copytestdest 'IPFILE_LIST'` is another workflow – bc81 May 11 '17 at 18:31
  • Yeah, that's completely doable, and it's also **completely off-topic in this question** since you didn't include that as part of the question when you asked it. Edits to questions that invalidate existing answers aren't acceptable here. – Charles Duffy May 11 '17 at 18:36
  • Here's a hint, though: Your real `copytestdest` will look like `dest:/foo` or at least `./foo`, right? So `[[ $1 = *[:/]* ]]` will distinguish whether it's a variable name or a destination, letting you easily add in the relevant branching logic. – Charles Duffy May 11 '17 at 18:39
  • Thanks for all the help! – bc81 May 11 '17 at 19:00