for NAME [in WORDS ... ] ; do COMMANDS; done
for 루프의 동작을 재정의하고 싶습니다. 즉, 구문의 의미를 변경할 때 기존 BASH 스크립트의 구문이 동일하게 유지되기를 원합니다. 예를 들어 나는 이런 사람이 COMMANDS
되고 싶다.coproc ( COMMANDS )
for i in $(seq 1 5);do sleep $[ 1 + $RANDOM % 5 ]; echo $i;done
~이 되다
for i in $(seq 1 5);do coproc (sleep $[ 1 + $RANDOM % 5 ]; echo $i);done
답변1
TCL은 할 수 있다그런 것들. bash
TCL이 아닙니다 ;-)
동시 coproc으로 인해 Stéphan의 깔끔한 zsh
솔루션 도 참조하십시오.zsh
alias do='do coproc {' done='}; done'
그게 아니라에 익숙해작동 bash
하지만 두 가지 문제가 있습니다. bash
단일 coproc만 올바르게 지원됩니다(아래 참조).~해야 한다여러 개의 코루틴이 있는 경우 코루틴의 이름을 지정하고 bash
현재 이름에 확장명을 적용하지 않으며(예: coproc $FOO
버그) 정적 이름만 허용합니다(해결 방법은 eval
).
그러나 이를 기반으로 실제로 그럴 필요가 없다고 가정하면 coproc
다음과 같이 할 수 있습니다.
shopt -s expand_aliases # enable aliases in a script
alias do='do (' done=')& done' # "do (" ... body... ")& done"
이는 do
각 주체를 배경 하위 쉘로 "상승"시킵니다. 루프의 맨 아래에서 동기화가 필요한 경우 a가 wait
트릭을 수행하거나 중요한 스크립트에 대해 더 선택적으로 처리합니다.
shopt -s expand_aliases
declare -a _pids
alias do='do (' done=')& _pids+=($!); done'
for i in $(seq 1 5);do sleep $[ 1 + $RANDOM % 5 ]; echo $i;done
wait ${_pids[*]}
_pids
배열의 새로운 각 하위 쉘을 추적합니다 .
(를 사용해 do { ... } &
도 작동하지만 추가 쉘 프로세스가 시작됩니다.)
참고하세요아마도 다음 중 어느 것도 프로덕션 코드에 나타나서는 안 됩니다!
원하는 것의 일부를 수행할 수 있지만(비록 우아하지 않고 강력하지는 않지만) 주요 문제는 다음과 같습니다.
- 공동 프로세스병렬화뿐만 아니라 다른 프로세스와의 동기식 상호 작용을 위해 설계되었습니다. (한 가지 효과는 stdin 및 stdout이 파이프로 변경되고 하위 쉘로서 다른 차이점이 있을 수 있다는 것입니다. 예를 들어
$$
) - 게다가 bash 전용(최대 4.4)단일 공동 프로세스 지원한 번.
- 신체가 백그라운드에서 실행 중인 경우 루프 제어를 중단하고 효과적으로 휴식을 취할 수 있습니다
break
(그러나 이는 여기의 간단한 예에는 영향을 미치지 않습니다).exit
동시 코프로세스의 불완전한 구현을 포함하여 bash 내부를 크게 수정하지 않고는 bash가 이를 수행하도록 할 방법이 없습니다. 명령은 구문 수준 구조에 암시적 ( )
으로 또는 로 명시적으로 그룹화되어야 합니다 . bash 스크립트를 전처리할 수 있지만 이를 위해서는 강력한 파서가 필요합니다(현재 파서에는 약 6500줄의 C/bison 입력이 있습니다).{ }
for/do/done
(온순한 독자들은 지금 눈을 돌리고 싶을 수도 있습니다.)
별칭과 함수를 사용하여 "재정의" 할 수 있는데 do
, 이 경우 많은 복잡한 문제(이스케이프, 인용, 확장 등)가 발생할 수 있습니다.
function _do()
{
( eval "$@" ) &
}
shopt -s expand_aliases # allow alias expansion in a script
alias do="do _do"
# single command only
for i in {1..5}; do sleep $((1+$RANDOM%5)); done
그러나 여러 명령을 전달하려면 다음을 이스케이프 및/또는 인용해야 합니다.
for i in {1..5}; do "sleep $((1+$RANDOM%5)); echo $i" ; done
이 경우 alias
트릭을 포기하고 직접 호출 _do
하거나 루프를 수정할 수도 있습니다.
(온순한 독자들은 이제 비명을 지르고 건물 밖으로 도망치고 싶을 수도 있습니다.)
이는 배열을 사용하는 오류 발생 가능성이 약간 적은 버전이지만 코드를 추가로 수정해야 합니다.
function _do()
{
local _cmd
printf -v _cmd "%s;" "$@"
( eval "$_cmd" ) &
}
cmd=( 'sleep $((1+$RANDOM%5))' )
cmd+=( 'echo $i' )
for i in {1..5}; do "${cmd[@]}" ; done
(이제 이 점잖은 독자는 기절했거나 더 이상의 피해를 입지 않을 것으로 생각됩니다.)
결국, 그것이 가능하기 때문이 아니라, 그것이 가능하기 때문이다.좋은 생각:
function _for() {
local _cmd _line
printf -v _cmd '%s ' "$@"
printf -v _cmd "for %s\n" "$_cmd" # reconstruct "for ..."
read _line # "do"
_cmd+=$'do (\n'
while read _line; do
_cmd+="$_line"; _cmd+=$'\n' # reconstruct loop body
done
_cmd+=$') &\ndone' # finished
unalias for # see note below
eval "$_cmd"
alias for='_for <<-"done"'
}
shopt -s expand_aliases
alias for='_for <<-"done"'
for i in $(seq 1 5)
do
sleep $((1+$RANDOM%5))
echo $i
done
필요한 유일한 변경 사항은 do
/가 done
자체 줄에 표시되고 done
0개 이상의 하드 탭으로 들여쓰기되어야 한다는 것입니다.
위의 내용은 다음과 같습니다.
- 하이재킹은
for
본문을done
. - 함수는
for
인수를 기반으로 루프를 다시 작성한 다음 HEREDOC에서 본문의 나머지 부분을 읽습니다. - 재구성된 몸체에는 "( ... ) &"가 삽입되어 있고
done
추가되었습니다 . - 호출 , 별칭을
eval
취소하려면 ( 접두사는 별칭에 적용됩니다.)for
\
주문하다, 키워드의 경우unalias
복원 필요)
다시 말하지만, 이는 그다지 강력하지 않습니다. 루프 리디렉션으로 인해 done > /dev/null
문제가 발생할 수 있고, 중첩으로 인해 문제가 발생할 수 있으며, 추가 참조가 필요한 연산으로 for
인해 문제가 발생할 수 있습니다. for (( ;; ))
재정의하는 데 동일한 트릭을 사용할 수 없습니다 do
. 단, select/do/done
작동하면 더 간단할 수 있지만 do
그렇게 하려고 하면 구문 오류(리디렉션 포함)가 발생합니다.