저는 쉘 스크립트를 작성하고 있습니다:
#!/bin/bash
i=1;
sp="/-\|";
no_config=true;
echo "type \"continue\" to exit this while loop";
echo "if you feel the conditions for continuing sucessfully have been met... ";
while $no_config;
do printf "\b${sp:i++%${#sp}:1}";
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false;
sleep 1;
done;
# do more stuff
이제 사용자가 시작한 루프 종료도 추가하고 싶습니다.
복잡해 보이네요.
처음에 나는 이것을 발견하고 희망이 높아졌습니다. https://unix.stackexchange.com/a/228152/228658 그러나 읽기 명령 실행이 중지된 것으로 나타났습니다.
나는 이것을 원하지 않습니다. 나는 언제든지 "q"를 누르면 종료되지만 위아래로 스크롤하는 기능을 방해하지 않는 매뉴얼 페이지와 같은 것을 원합니다. 백그라운드에서 입력을 신중하게 기다리고 있습니다.
쉘 스크립트에서 이를 어떻게 달성할 수 있습니까?
답변1
더 간단한 해결책은 사용자에게 Ctrl-C
루프 종료를 눌러 SIGINT
신호를 보내고 스크립트를 종료하도록 알리는 것입니다.
스크립트를 종료하기 전에 다른 작업을 수행하려면 다음 신호를 잡을 수 있습니다.
cleanup() {
# ...
exit 0
}
trap 'cleanup' INT
또는 하위 쉘에서 쉘 중 하나를 실행하여 쉘 중 하나가 종료되면 다른 쉘도 종료되도록 할 수 있습니다. 이렇게 하면 입력 중 하나를 차단하고 다른 입력은 다른 검사를 수행할 수 있습니다.
#!/bin/bash
i=1
sp="/-\|"
no_config=true
echo "type \"continue\" to exit this while loop if you feel the conditions for continuing sucessfully have been met... "
(
while $no_config
do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
kill $$
) &
child_pid=$!
while $no_config
do
read -r typed_continue
[[ "$typed_continue" = "continue" ]] && no_config=false
sleep 1
done
kill $child_pid
답변2
불행히도 Bash는 이러한 제어에 적합하지 않습니다.
그러나 작동하는 것을 얻을 수 있는 방법이 있습니다.
가장 단순한 것부터 가장 복잡한 것까지 몇 가지를 소개합니다.
종료하려면 Ctrl+C를 입력하세요.
루프가 Ctrl+C에서 종료 되도록 Ctrl+C(INT 신호를 잡아 처리)를 설치하여 trap
값 을 no_config
설정할 수 있습니다 .false
while
i=1
sp='/-\|'
no_config=true
echo "type Ctrl+C to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
trap 'no_config=false' INT
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
trap 'trap - INT; kill -INT $$' INT
echo "do more stuff"
(대화형 쉘에서 위 코드를 실행하는 경우기억하다코드의 기능으로 인해 쉘이 오염되지 않도록 (
전체 코드를 선행 및 후행으로 묶습니다. )
)
while
루프 내에서 모든 명령이 완료된 경우에만 루프가 중단됩니다 $no_config
. 이는 while
루프 내에서 수행하는 작업에 따라 이 동작을 원할 수도 있고 원하지 않을 수도 있습니다. 귀하의 예에서 실제 "느린" 작업은 명시적이므로 sleep 1
갑자기 중단할 필요는 없지만 어쨌든 루프를 중단하려면 가장 간단한 부록은 전체 다이를 종료할 수 있는 하위 쉘을 사용하는 것입니다. 이와 같이:
trap 'kill $!' INT
(
i=1
sp='/-\|'
no_config=true
echo "type Ctrl+C to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
) &
wait
trap 'trap - INT; kill -INT $$' INT
echo "do more stuff"
여기서는 "더 많은 작업 수행" 부분을 계속 trap
하기 위해 하위 쉘을 종료하도록 INT(Ctrl+C)를 설정했습니다 .wait
그러나 이 방법으로 루프 내부에 설정된 변수는 "더 많은 작업" 부분으로 전파되지 않습니다. 이 문제를 해결하려면 임시 파일이나 명명된 FIFO와 같은 보조 통신이 필요합니다.
종료하려면 "q"를 입력하세요.
read
임의의 문자로 종료할 수 있도록 하는 위 방법의 대안(따라서 Ctrl+C도 일반 작업으로 유지)은 루프에 비차단 기능을 포함시키는 것입니다(Bash v4+에서 사용 가능). 이를 위해서는 터미널 설정에 대한 일부 보조 준비도 필요합니다. 전체 내용은 다음과 같습니다.
term_settings="$(stty -g)"
trap 'stty "${term_settings}"' EXIT
i=1
sp='/-\|'
no_config=true
echo "type exactly \"q\" to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
stty -icanon -echo
while $no_config; do
while read -t 0 && read -rN 1 ; do [ "${REPLY}" = q ] && break 2 || true; done
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
stty "${term_settings}"
echo "do more stuff"
여기서는 나중에 종료 시 복원할 수 있도록 먼저 터미널의 현재 설정을 저장한 다음 입력된 문자 표시( -echo
) 및 줄 기반 키보드 입력( -icanon
)을 비활성화합니다. while
루프 에서는 read -t 0
(non-blocking )을 사용하여 입력된 문자가 있는지 확인합니다. 이 경우에는 루프를 종료 하려는 문자가 있는지 테스트하기 위해 read
하나씩 읽어( ) 있습니다.-N 1
q
read -t 0
루프의 각 반복이 완료되므로 약간의 계산 오버헤드가 추가됩니다 while
. 귀하의 예에서는 오버헤드가 무시할 수 있습니다.
를 사용하여 갑자기 중단 가능한 루프를 수행하려면 type q to quit
다른 접근 방식을 취해야 하며 포그라운드 코드에서 read
a를 기다리는 동안 백그라운드 코드에서 코드의 중단 가능한 부분을 실행해야 하기 때문에 상황이 더 복잡해집니다. read
백그라운드 코드가 주기적으로 완료되면 잠금을 해제하는 방법이 필요합니다 .
이를 달성하려면 다음과 같이 Bash 기능에 대한 광범위한 확장이 필요합니다.
- 코드의 인터럽트 가능한 부분을 백그라운드 서브셸에 별도로 캡슐화합니다. 이전에 했던 것과 약간 비슷하지만 특별한 주의가 필요합니다.
- 배경 하위 쉘에서 전경으로 Ctrl+C를 시뮬레이션합니다
read
.
while
이는 또한 이전에 말했듯이 루프에서 "더 많은 작업" 부분으로 변수를 전달할 수 없다는 것을 의미합니다 (의도적으로 보조 통신을 설정해야 함).
예는 다음과 같습니다.
#!/bin/bash
term_settings="$(stty -g)"
(
trap 'stty "${term_settings}"' EXIT
_keyboard_waiter=$BASHPID
(
i=1
sp='/-\|'
no_config=true
echo "type exactly \"q\" to exit this while loop"
echo "if you feel the conditions for continuing successfully have been met... ";
while $no_config; do
printf "\b${sp:i++%${#sp}:1}"
[[ ! $(pidof SupremeCommande) && -f ~/My\ Documents/newfile ]] && no_config=false
sleep 1
done
kill -INT $_keyboard_waiter
) &
while read -rsN 1 && ! [ "${REPLY}" = q ] ; do :; done
{ kill $! && wait; } 2>/dev/null
)
echo "do more stuff"
여기에는 다음이 있습니다.
- 신호를 처리해야 하므로 스크립트 파일을 통해 모든 것을 더 잘 사용할 수 있습니다.
read
중단 시 필요하며 나중에 종료 시 복원할 수 있도록 시작 시 터미널 설정이 저장되었습니다.- 서브쉘은 포그라운드에서 실행되며 키보드 입력을 기다리는 역할을 합니다.
- 하위 하위 쉘은 코드의 중단 가능한 부분을 위해 백그라운드(&)에서 실행됩니다.
kill -INT
배경 서브쉘read
인터럽트에 사용되는 서브쉘입니다. 이는 Bash에서 처리하는 해당 하위 쉘에 대해 Ctrl+C를 에뮬레이트합니다.- 줄 바꿈( )에 관계없이 한 문자만 대기/수락하는 동안
read
입력된 문자( )를 표시하지 않는 옵션 과 백슬래시를 이스케이프 문자로 해석하지 않는 일반적인 옵션-s
-N 1
-r
- "q"가 실제로 입력되면 배경 서브셸을 간단히 "kill&wait"합니다.