bash 내장 루프 표현식 동작을 재정의하는 방법은 무엇입니까?

bash 내장 루프 표현식 동작을 재정의하는 방법은 무엇입니까?
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은 할 수 있다그런 것들. bashTCL이 아닙니다 ;-)

동시 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자체 줄에 표시되고 done0개 이상의 하드 탭으로 들여쓰기되어야 한다는 것입니다.

위의 내용은 다음과 같습니다.

  • 하이재킹은 for본문을 done.
  • 함수는 for인수를 기반으로 루프를 다시 작성한 다음 HEREDOC에서 본문의 나머지 부분을 읽습니다.
  • 재구성된 몸체에는 "( ... ) &"가 삽입되어 있고 done추가되었습니다 .
  • 호출 , 별칭을 eval취소하려면 ( 접두사는 별칭에 적용됩니다.)for\주문하다, 키워드의 경우 unalias복원 필요)

다시 말하지만, 이는 그다지 강력하지 않습니다. 루프 리디렉션으로 인해 done > /dev/null문제가 발생할 수 있고, 중첩으로 인해 문제가 발생할 수 있으며, 추가 참조가 필요한 연산으로 for인해 문제가 발생할 수 있습니다. for (( ;; ))재정의하는 데 동일한 트릭을 사용할 수 없습니다 do. 단, select/do/done작동하면 더 간단할 수 있지만 do그렇게 하려고 하면 구문 오류(리디렉션 포함)가 발생합니다.

관련 정보