![为什么我的 Bash 过滤功能不起作用?](https://linux22.com/image/169765/%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84%20Bash%20%E8%BF%87%E6%BB%A4%E5%8A%9F%E8%83%BD%E4%B8%8D%E8%B5%B7%E4%BD%9C%E7%94%A8%EF%BC%9F.png)
我写了一个筛选/选择将函数和流作为输入的函数。它应该产生一个新的数组 ( 2 4 )。然而我的结果却是一无所获。我怀疑这与IFS有关。
# func int -> bool
is_even () { (( $1 % 2 == 0 )) ;}
# func func -> int
filter () {
local function_to_apply=$1
local arg
while read -r arg; do
$function_to_apply $arg && echo $arg
done;:
}
# int array
integers=( 1 2 3 4 )
result=$(echo "${integers[*]}" | filter is_even)
declare -p result
输出是一个字符串""
declare -- result=""
预期输出是一个数组( 2 4 )
declare -a result ='([0]="2" [1]="4")'
在应得的信用处给予信用:
http://www.binaryphile.com/bash/2018/07/26/approach-bash-like-a-developer-part-1-intro.html
答案1
您可以在一行上给出函数输入。该行将是使用默认值 展开1 2 3 4
的字符串。整个单行将由第一次调用into读取,并在函数调用中使用(不加引号)。由于是 未加引号的,因此 shell 会将字符串吐出到空格上,并且您的函数将仅使用该首字母,而该首字母不是偶数。这意味着未触发。"${integers[*]}"
$IFS
read
arg
$arg
1
echo $arg
反而:
#!/bin/bash
is_even () { (( $1 % 2 == 0 )) ;}
filter () {
local function_to_apply="$1"
local arg
while read -r arg; do
"$function_to_apply" "$arg" && echo "$arg"
done
}
integers=( 1 2 3 4 )
result=$(printf '%s\n' "${integers[@]}" | filter is_even)
declare -p result
这里的主要任务是将数组的元素打印到filter
函数的各个行上。
这将为您提供单个字符串2\n4
(其中\n
是换行符)。当您将字符串分配给 时,这应该不足为奇result
。
如果您想取回数组,可以在最新版本中执行此操作bash
:
#!/bin/bash
is_even () { (( $1 % 2 == 0 )) ;}
filter () {
local func="$1"
local -n in_array="$2"
local -n out_array="$3"
local element
out_array=()
for element in "${in_array[@]}"; do
if "$func" "$element"; then
out_array+=( "$element" )
fi
done
}
integers=( 1 2 3 4 )
even_ints=()
filter is_even integers even_ints
declare -p even_ints
这是在函数内使用两个名称引用变量filter
。第一个是输入数组,第二个是输出数组。
这会给你输出
declare -a even_ints=([0]="2" [1]="4")
将值传递给filter
函数的另一种方法显然是在函数的命令行上传递它们:
#!/bin/bash
is_even () { (( $1 % 2 == 0 )) ;}
filter () {
local func="$1"
local -n out_array="$2"
shift 2
local element
out_array=()
for element do
if "$func" "$element"; then
out_array+=( "$element" )
fi
done
}
integers=( 1 2 3 4 )
even_ints=()
filter is_even even_ints "${integers[@]}"
declare -p even_ints
答案2
1.@Kusalananda'sfilter
将无法就地过滤数组;如果相同的数组被指定为源和目标,它只会截断它。
通过重写函数可以很容易地解决这个问题:
filter() {
local cb=$1 i j a; local -n src=$2 dst=$3
for a in "${src[@]}"; do
"$cb" "$a" && dst[i++]=$a
done
for((j=${#dst[@]}; j>=i; j--)); do
unset dst[j]
done
}
2.然而,这样的演示/挑战(例如“如何在 bash 中进行函数式编程”)是完全没有意义。即使与其他非常高级的语言(如 perl 或 javascript)相比,bash 也非常慢。使用 bash 解析/过滤数据而不是调用其他实用程序grep
可能会慢几个数量级。
使用问题中的问题,即使使用微不足道的 1000 个元素的数组并且在过滤时,grep 解决方案也会快 3 倍bash 自己的数组,而不是一些外部文件。尽管必须分叉 3 个进程并执行外部二进制文件。请参阅下面的示例(使用 100000 个元素的数组):
bash filter.sh 100000
=== bash_filter ===
real 0m1.037s
user 0m1.036s
sys 0m0.001s
=== grep_filter ===
real 0m0.302s
user 0m0.226s
sys 0m0.189s
过滤器.sh:
is_even () { (( $1 % 2 == 0 )) ;}
bash_filter() {
local cb=$1 i j a; local -n src=$2 dst=$3
for a in "${src[@]}"; do
"$cb" "$a" && dst[i++]=$a
done
for((j=${#dst[@]}; j>=i; j--)); do
unset dst[j]
done
}
grep_filter() {
local flt=$1; local -n src=$2 dst=$3
dst=($(printf '%d\n' "${src[@]}" | grep "$flt"))
}
timeit(){
echo "=== $1 ==="
time "$@" inlist outlist
[ "${#outlist[@]}" -lt 20 ] && echo "${outlist[@]}"
}
inlist=($(seq "${1-100000}"))
timeit bash_filter is_even
timeit grep_filter '[02468]$'