如何为任意命令参数动态替换数组条目?

时间:2021-08-15 21:17:48

This question is inspired by bash nested variable in for loop.

这个问题的灵感来自for循环中的bash嵌套变量。

If I have an array in bash, and I want to be able to run an arbitrary command for every element of that array, is there a way to do that via a generic function, as opposed to with a loop? That is:

如果我在bash中有一个数组,并且我希望能够为该数组的每个元素运行任意命令,那么有没有办法通过泛型函数来实现,而不是使用循环?那是:

dest_array=( host1:/foo host2:/bar host3:/baz )
copy ./file dest_array

and have each expansion called:

每个扩展称为:

copy ./file host1:/foo
copy ./file host2:/bar
copy ./file host3:/baz

Even better, is there a way to do this for multiple arrays? For instance:

更好的是,有没有办法为多个阵列执行此操作?例如:

sources=( first second )
dests=( host1:/foo host2:/bar )
copy sources dests

invoking (in no particular order):

调用(没有特定的顺序):

copy first host1:/foo
copy first host2:/bar
copy second host1:/foo
copy second host2:/bar

2 个解决方案

#1


2  

Consider the following function, written for bash 4.3 or later:

考虑以下函数,为bash 4.3或更高版本编写:

run_for_each() {
  local -n _items=$1; shift
  local sigil=$1; shift
  local -a args=( "$@" )
  local -a call
  local retval=0
  for item in "${_items[@]}"; do
    call=( "${args[@]//$sigil/$item}" )
    "${call[@]}" || (( retval |= $? ))
  done
  return "$retval"
}

As an example of usage:

作为使用示例:

sources=( first second )
dests=( host1:/foo host2:/bar )

run_for_each sources SOURCE \
  run_for_each dests DEST \
    rsync -Pv SOURCE DEST

If you wanted to make it concurrent, that might look like:

如果你想让它并发,那可能看起来像:

run_for_each_concurrent() {
  local -n _items=$1; shift
  local sigil=$1; shift
  local -a args=( "$@" )
  local -a pids=( )
  local -a call
  local retval=0
  for item in "${_items[@]}"; do
    call=( "${args[@]//$sigil/$item}" )
    "${call[@]}" & pids+=( "$!" )
  done
  for pid in "${pids[@]}"; do
    wait "$pid" || (( retval |= $? ))
  done
  return "$retval"
}

...which will run one process per array entry, all at the same time; wait for them all to exit; and return the ORed-together exit status of all those subprocesses.

...每个数组条目将同时运行一个进程;等他们全部退出;并返回所有这些子进程的ORed-together退出状态。


Portability Modifications (Adapting for Bash 3.2 Compatibility)

By the way -- if you don't have bash 4.3, the above can be made to work with older releases by replacing this line:

顺便说一句 - 如果你没有bash 4.3,可以通过替换以下行来使上述版本适用于旧版本:

local -n _items=$1; shift

with the following instead:

改为:

printf -v cmd 'local -a _items=( "${%q[@]}" )' "$1" && eval "$cmd"; shift

#2


1  

What do you think about this solution:

您如何看待这个解决方案:

run_for() {
   local -n _sources=$1; shift
   local src_label=$1; shift
   local -n _dests=$1; shift
   local dst_label=$1; shift

   local -a cmd=( "$@" ) execute
   local retval=0

   for src_item in "${_sources[@]}"; do
      for dst_item in "${_dests[@]}"; do
        execute=()
        for cmd_item in "${cmd[@]}"; do
           case $cmd_item in
              $src_label) execute+=("$src_item") ;;
              $dst_label) execute+=("$dst_item") ;;
                       *) execute+=("$cmd_item") ;;
           esac 
        done          
        "${execute[@]}" || (( retval |= $? ))
      done

   done

   return "$retval"
}

This solution also works with bash 4.3 or later and uses for loop 3 times (not really pretty). However, it has a more straightforward usage and handles the following case correctly:

此解决方案也适用于bash 4.3或更高版本,并使用循环3次(不是很漂亮)。但是,它具有更直接的用法并正确处理以下情况:

sources=( first second DEST )
dests=( host1 host2 host3 )

run_for sources SOURCE dests DEST echo "<<" SOURCE - DEST ">>"

This solution cannot handle the following:

此解决方案无法处理以下内容:

run_for sources SOURCE dests DEST echo aaSOURCE - DESTaa

Although, I would not really consider aaSOURCE to be a valid label. If it is, the following could, in my opinion, copy the behavior of your solution while still preserving the more straightforward usage. It will also have the same drawback when one of the sources is equal to $dst_label:

虽然,我不会真的认为aaSOURCE是一个有效的标签。如果是,在我看来,以下内容可以复制您的解决方案的行为,同时仍然保留更直接的用法。当其中一个源等于$ dst_label时,它也会有同样的缺点:

for src_item in "${_sources[@]}"; do

   tmp=("${cmd[@]//$src_label/$src_item}")    
   for dst_item in "${_dests[@]}"; do
      execute=("${tmp[@]//$dst_label/$dst_item}")
      "${execute[@]}" || (( retval |= $? ))
   done

done

#1


2  

Consider the following function, written for bash 4.3 or later:

考虑以下函数,为bash 4.3或更高版本编写:

run_for_each() {
  local -n _items=$1; shift
  local sigil=$1; shift
  local -a args=( "$@" )
  local -a call
  local retval=0
  for item in "${_items[@]}"; do
    call=( "${args[@]//$sigil/$item}" )
    "${call[@]}" || (( retval |= $? ))
  done
  return "$retval"
}

As an example of usage:

作为使用示例:

sources=( first second )
dests=( host1:/foo host2:/bar )

run_for_each sources SOURCE \
  run_for_each dests DEST \
    rsync -Pv SOURCE DEST

If you wanted to make it concurrent, that might look like:

如果你想让它并发,那可能看起来像:

run_for_each_concurrent() {
  local -n _items=$1; shift
  local sigil=$1; shift
  local -a args=( "$@" )
  local -a pids=( )
  local -a call
  local retval=0
  for item in "${_items[@]}"; do
    call=( "${args[@]//$sigil/$item}" )
    "${call[@]}" & pids+=( "$!" )
  done
  for pid in "${pids[@]}"; do
    wait "$pid" || (( retval |= $? ))
  done
  return "$retval"
}

...which will run one process per array entry, all at the same time; wait for them all to exit; and return the ORed-together exit status of all those subprocesses.

...每个数组条目将同时运行一个进程;等他们全部退出;并返回所有这些子进程的ORed-together退出状态。


Portability Modifications (Adapting for Bash 3.2 Compatibility)

By the way -- if you don't have bash 4.3, the above can be made to work with older releases by replacing this line:

顺便说一句 - 如果你没有bash 4.3,可以通过替换以下行来使上述版本适用于旧版本:

local -n _items=$1; shift

with the following instead:

改为:

printf -v cmd 'local -a _items=( "${%q[@]}" )' "$1" && eval "$cmd"; shift

#2


1  

What do you think about this solution:

您如何看待这个解决方案:

run_for() {
   local -n _sources=$1; shift
   local src_label=$1; shift
   local -n _dests=$1; shift
   local dst_label=$1; shift

   local -a cmd=( "$@" ) execute
   local retval=0

   for src_item in "${_sources[@]}"; do
      for dst_item in "${_dests[@]}"; do
        execute=()
        for cmd_item in "${cmd[@]}"; do
           case $cmd_item in
              $src_label) execute+=("$src_item") ;;
              $dst_label) execute+=("$dst_item") ;;
                       *) execute+=("$cmd_item") ;;
           esac 
        done          
        "${execute[@]}" || (( retval |= $? ))
      done

   done

   return "$retval"
}

This solution also works with bash 4.3 or later and uses for loop 3 times (not really pretty). However, it has a more straightforward usage and handles the following case correctly:

此解决方案也适用于bash 4.3或更高版本,并使用循环3次(不是很漂亮)。但是,它具有更直接的用法并正确处理以下情况:

sources=( first second DEST )
dests=( host1 host2 host3 )

run_for sources SOURCE dests DEST echo "<<" SOURCE - DEST ">>"

This solution cannot handle the following:

此解决方案无法处理以下内容:

run_for sources SOURCE dests DEST echo aaSOURCE - DESTaa

Although, I would not really consider aaSOURCE to be a valid label. If it is, the following could, in my opinion, copy the behavior of your solution while still preserving the more straightforward usage. It will also have the same drawback when one of the sources is equal to $dst_label:

虽然,我不会真的认为aaSOURCE是一个有效的标签。如果是,在我看来,以下内容可以复制您的解决方案的行为,同时仍然保留更直接的用法。当其中一个源等于$ dst_label时,它也会有同样的缺点:

for src_item in "${_sources[@]}"; do

   tmp=("${cmd[@]//$src_label/$src_item}")    
   for dst_item in "${_dests[@]}"; do
      execute=("${tmp[@]//$dst_label/$dst_item}")
      "${execute[@]}" || (( retval |= $? ))
   done

done