代码之家  ›  专栏  ›  技术社区  ›  Socowi

每个数组项的进程替换,不带eval

  •  3
  • Socowi  · 技术社区  · 7 年前

    例如,我有一个任意字符串数组 a=(1st "2nd string" $'3rd\nstring\n' ...) 是的。
    我想将这些字符串传递给一个将其参数解释为文件的命令,例如 paste 是的。

    对于固定数量的变量,我们可以使用进程替换

    paste <(printf %s "$var1") <(printf %s "$var2") <(printf %s "$var3")
    

    但只有事先知道变量的数目,这才起作用。
    对于阵列 a ,我们可以写一些相当安全的东西,比如

    eval paste $(printf '<(printf %%s %q) ' "${a[@]}")
    

    不感兴趣:有没有一种方法可以替代 的条目,而不使用 eval 是吗?记住这一点 的条目可以包含任何字符(除了 \0 因为 bash 不支持它)。

    2 回复  |  直到 7 年前
        1
  •  3
  •   rici    7 年前

    这是一个如何使用递归设置参数列表的示例。这种技术有时是有用的。

    使用流程替换将文本转换为管道可能不是当前问题的最佳解决方案,但它确实具有重用现有工具的优点。

    我试图使代码合理通用,但有可能需要做更多的调整。

    nameref需要bash 4.3(如果还没有达到该版本,可以使用固定的数组名)。nameref需要小心,因为它们不卫生;可以按名称捕获局部变量。因此使用以下划线开头的变量名。

    # A wrapper which sets up for the recursive call
    from_array() {
      local -n _array=$1
      local -a _cmd=("${@:2}")
      local -i _count=${#_array[@]}
      from_array_helper
    }
    
    # A recursive function to create the process substitutions.
    # Each invocation adds one process substitution to the argument
    # list, working from the end.
    from_array_helper() {
      if (($_count)); then
        ((--_count))
        from_array_helper <(printf %s "${_array[_count]}") "$@"
      else
        "${_cmd[@]}" "$@"
      fi
    }
    

    例子

    $ a=($'first\nsecond\n' $'x\ny\n' $'27\n35\n')
    $ from_array a paste -d :
    first:x:27
    second:y:35
    
        2
  •  0
  •   Socowi    7 年前

    这个解决方案的灵感来自 rici's answer 是的。 它解决了namerefs可能导致的名称冲突,但要求用户指定一个在要执行的命令中不出现的分隔符。但是,分隔符可以毫无问题地出现在数组中。

    # Search a string in an array
    # and print the 0-based index of the first identical element.
    # Usage: indexOf STRING "${ARRAY[@]}"
    # Exits with status 1 if the array does not contain such an element.
    indexOf() {
        search="$1"
        i=0
        while shift; do
            [[ "$1" = "$search" ]] && echo "$i" && return
            ((++i))
        done
        return 1
    }
    
    # Execute a command and replace its last arguments by anonymous files.
    # Usage: emulateFiles DELIMITER COMMAND [OPTION]... DELIMITER [ARGUMENT]...
    # DELIMITER must differ from COMMAND and its OPTIONS.
    # Arguments after the 2nd occurrence of DELIMITER are replaced by anonymous files.
    emulateFiles() {
        delim="$1"
        shift
        i="$(indexOf "$delim" "$@")" || return 2
        cmd=("${@:1:i}")
        strings=("${@:i+2}")
        if [[ "${#strings[@]}" = 0 ]]; then
            "${cmd[@]}"
        else
            emulateFiles "$delim" "${cmd[@]}" <(printf %s "${strings[0]}") \
                         "$delim" "${strings[@]:1}"
        fi
    }
    

    使用示例

    a=($'a b\n c ' $'x\ny\nz\n' : '*')
    $ emulateFiles : paste : "${a[@]}"
    a b x   :   *
     c  y       
        z       
    $ emulateFiles : paste -d: : "${a[@]}"   # works because -d: != :
    a b:x:::*
     c :y::
    :z::
    $ emulateFiles delim paste -d : delim "${a[@]}"
    a b:x:::*
     c :y::
    :z::