![Bash 필터 기능이 작동하지 않는 이유는 무엇입니까?](https://linux55.com/image/169765/Bash%20%ED%95%84%ED%84%B0%20%EA%B8%B0%EB%8A%A5%EC%9D%B4%20%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80%20%EC%95%8A%EB%8A%94%20%EC%9D%B4%EC%9C%A0%EB%8A%94%20%EB%AC%B4%EC%97%87%EC%9E%85%EB%8B%88%EA%B9%8C%3F.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
기본값으로 확장된 문자열 입니다 . 첫 번째 호출에서 전체 단일 행을 읽고 함수 호출에서 (따옴표 없이) 사용합니다. 따옴표가 없기 때문에 쉘은 공백에 문자열을 표시하고 함수는 짝수가 아닌 첫 번째 문자만 사용합니다. 이는 트리거되지 않았음을 의미합니다."${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의 filter
배열을 제자리에서 필터링할 수 있는 방법은 없습니다. 동일한 배열이 소스와 대상으로 지정된 경우에는 배열이 잘립니다.
이 문제는 함수를 다시 작성하면 쉽게 해결할 수 있습니다.
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]$'