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

问题描述 投票:1回答:2

这个问题的灵感来自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
arrays bash
2个回答
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退出状态。


Portability Modifications (Adapting for Bash 3.2 Compatibility)

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

local -n _items=$1; shift

改为:

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

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
© www.soinside.com 2019 - 2024. All rights reserved.