每个数组条目的过程替换,没有评估
Process Substitution For Each Array Entry, Without Eval
我有一个任意字符串数组,例如 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
的情况下处理替换 a
的每个条目?请记住,a
的条目可以包含任何字符([=18=]
除外,因为 bash
不支持它)。
这是一个示例,说明如何使用递归一次一个参数地设置参数列表。该技术偶尔会有用。
使用流程替换将文本转换为管道可能不是解决手头问题的最佳方法,但它确实具有重用现有工具的优点。
我试图使代码合理通用,但可能需要进行更多调整。
Bash nameref 需要 4.3(尽管如果您尚未达到该版本,则可以使用固定的数组名称来完成)。 Namerefs 需要小心,因为它们不卫生;可以按名称捕获局部变量。因此使用以下划线开头的变量名。
# A wrapper which sets up for the recursive call
from_array() {
local -n _array=
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
此解决方案的灵感来自 。
它解决了 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=""
i=0
while shift; do
[[ "" = "$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=""
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::
我有一个任意字符串数组,例如 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
的情况下处理替换 a
的每个条目?请记住,a
的条目可以包含任何字符([=18=]
除外,因为 bash
不支持它)。
这是一个示例,说明如何使用递归一次一个参数地设置参数列表。该技术偶尔会有用。
使用流程替换将文本转换为管道可能不是解决手头问题的最佳方法,但它确实具有重用现有工具的优点。
我试图使代码合理通用,但可能需要进行更多调整。
Bash nameref 需要 4.3(尽管如果您尚未达到该版本,则可以使用固定的数组名称来完成)。 Namerefs 需要小心,因为它们不卫生;可以按名称捕获局部变量。因此使用以下划线开头的变量名。
# A wrapper which sets up for the recursive call
from_array() {
local -n _array=
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
此解决方案的灵感来自
# 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=""
i=0
while shift; do
[[ "" = "$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=""
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::