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

时间:2017-05-11 22:03:00

标签: arrays bash

此问题的灵感来自bash nested variable in for loop

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

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

并将每个扩展称为:

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

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

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

调用(无特定顺序):

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

2 个答案:

答案 0 :(得分:2)

考虑以下为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"
}

作为使用示例:

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

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

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

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"
}

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

可移植性修改(适应Bash 3.2兼容性)

顺便说一下 - 如果您没有bash 4.3,可以通过替换以下行来使上述版本与以上版本一起使用:

local -n _items=$1; shift

改为:

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

答案 1 :(得分:1)

您对此解决方案有何看法:

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"
}

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

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

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

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

run_for sources SOURCE dests DEST echo aaSOURCE - DESTaa

虽然,我不认为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