Job Control
터미널에서 단순히 한번에 하나의 명령만 실행시킬 수 있는 것이 아니라 &
메타문자를 이용해 background job 을 생성함으로써 멀티태스킹을 할 수 있습니다. 가령 인터넷에서 파일을 다운로드하는 job 을 background 로 실행시켜놓고 동시에 vi 에디터로 파일 수정 작업을 할 수 있습니다.
Job id 와 Job specification
$ curl -sO http://cdimage.ubuntu.com/.../ubuntu-mate-15.10-desktop-amd64.iso &
[3] 26558
명령을 &
메타문자를 이용해 background 로 실행시키면 결과로 job id 와 process id 를 보여줍니다. 위의 예에서 [3]
부분이 job id 에 해당되고 26558
은 process id (pid) 에 해당됩니다.
만약에 kill 명령을 사용해 job 에 신호를 보낼때 job id 를 사용한다면 pid 와 구분할 수 없게 됩니다 ( 둘 다 숫자이므로 ). 그래서 job id 대신 job specification ( 줄여서 jobspec ) 을 사용하는데 이때 jobspec 은 job id 앞에 %
문자를 붙여서 만듭니다. ( 예: %3
)
이 jobspec 은 job control 에 사용되는 명령들 ( jobs, bg, fg, wait, disown, kill ) 에서 사용됩니다. jobspec 을 이용해 kill 명령으로 신호를 보내면 같은 pgid ( process group id ) 를 갖는 프로세스 들에게 모두 전달되므로 만약에 child process 가 생성되어 실행 중이라면 함께 종료하게 됩니다.
Jobspec 과 pid 가 다른점
pid 는 개별 프로세스를 나타내지만 jobspec 은 파이프로 연결된 모든 프로세스를 포함합니다.
# 파이프로 연결된 경우 마지막 명령의 pid 를 표시
$ sleep 10 | sleep 10 | sleep 10 &
[1] 12782
# jobspec 은 파이프로 연결된 3 개의 프로세스를 모두 포함.
$ jobs %1
[1]+ Running sleep 10 | sleep 10 | sleep 10 &
# pid 는 개별 프로세스를 나타냄.
$ ps f
PID TTY STAT TIME COMMAND
...
1643 pts/10 Ss 0:00 bash
12780 pts/10 S 0:00 \_ sleep 10 # pid
12781 pts/10 S 0:00 \_ sleep 10 # pid
12782 pts/10 S 0:00 \_ sleep 10 # pid
...
jobs
jobs [-lnprs] [jobspec ...] or jobs -x command [args]
현재 job table 목록을 보여줍니다. -l
옵션을 주면 process id 도 함께 보여줍니다. job id 옆에 보이는 +
, -
기호는 jobspec 에 사용되며 %+
는 current job 그러니까 가장 최근에 background 상태가된 job 을 나타내고 %-
는 previous job 을 나타냅니다. fg 명령을 사용해 이동함에 따라 +
, -
위치도 바뀌게 됩니다.
jobspec %%
은 %+
와 동일한 의미를 가집니다.
$ vi 111 &
$ vi 222 &
$ vi 333 &
$ jobs # vi 로 3 개의 파일을 열고난 후의 상태
[1] Stopped vi 111
[2]- Stopped vi 222 # previous job
[3]+ Stopped vi 333 # current job ( 가장 최근 job )
$ fg %1 # %1 로 이동
# vi 화면에서 ctrl-z
$ jobs
[1]+ Stopped vi 111 # current
[2] Stopped vi 222
[3]- Stopped vi 333 # previous
$ fg %2 # %2 로 이동
# vi 화면에서 ctrl-z
$ jobs
[1]- Stopped vi 111 # previous
[2]+ Stopped vi 222 # current
[3] Stopped vi 333
-p
옵션은 job table 에서 pid 만 표시합니다.
파이프로 연결된 명령 그룹일 경우 첫번째 pid 만 표시됩니다.
fg
fg [jobspec]
현재 background 에 stopped 또는 running 상태에 있는 job 을 foreground 로 실행하고 current job 으로 만듭니다.
그러므로 이후에 ctrl-z 로 stopped 되었을때 job table 에는 +
로 표시됩니다.
jobspec 을 인수로 주지 않으면 current job ( +
표시된 job ) 이 사용됩니다.
bg
bg [jobspec ...]
Ctrl-z ( SIGTSTP ) 에 의해 현재 stopped 상태에 있는 background job 에 SIGCONT 신호를 보내 background running 상태로 만듭니다. jobspec 을 인수로 주지 않으면 current job 이 사용됩니다.
suspend
suspend [-f]
suspend 명령을 실행하는 shell 은 이후에 SIGCONT 신호를 받기 전까지 중단됩니다.
login shell 에서는 사용할 수 없으나 -f
옵션을 이용하면 override 할 수 있습니다.
disown
disown [-h] [-ar] [jobspec ...]
disown 은 "이 job 은 내것이 아니다" 라고 선언합니다. job 이 종료되는 것은 아니고 결과로 job table 목록에서 삭제되어 더이상 control 할 수 없게 됩니다. 터미널 프로그램을 종료시킬때, 또는 login shell 에서 exit 시에 HUP 시그널에 의해 job 이 종료되는 것을 방지할 수 있습니다 ( shopt -s huponexit
옵션이 설정되어 있을경우 ). -h
옵션도 동일한 역할을 하지만 job table 에는 계속 남아있으므로 control 할 수 있습니다.
wait
wait [-n] [jobspec or pid …]
Background 로 실행되는 job 이 종료될 때까지 기다립니다. child 프로세스에 해당할 경우만 wait 할 수 있습니다. 인수로 jobspec 이나 pid 를 주게되면 해당 job 이 종료될 때까지 기다린 후 종료 상태 값을 리턴합니다. 인수 없이 실행하면 모든 프로세스를 기다리며 종료 값으로는 0 을 리턴합니다. 특정 작업이 완료된후에 다음단계로 진행해야 할경우 사용할 수 있습니다.
$!
변수는 가장 최근에 background 로 실행된 pid 값을 나타냅니다.|
파이프로 연결된 명령 그룹일 경우 마지막 명령의 pid 가 되며 전체 pipeline 이 종료될 때까지 wait 합니다.
#!/bin/bash
( sleep 1; exit 3 ) &
wait $!
echo $?
########### output ###########
3
---------------------------------------
#!/bin/bash
( echo start process 1...; sleep 3; echo end process 1.; exit 1 ) &
( echo start process 2...; sleep 2; echo end process 2.; exit 2 ) &
( echo start process 3...; sleep 4; echo end process 3.; exit 3 ) &
wait # 3 개의 background process 를 모두 기다림.
echo exit status: $?
########### output ###########
start process 1...
start process 2...
start process 3...
end process 2.
end process 1.
end process 3.
exit status: 0 # 종료 상태값이 0 이 됨
------------- wait 활용 ---------------
#!/bin/bash
echo main start ...
(
( echo start process 1...; sleep 3; echo end process 1; exit 1 ) &
wait $!
echo process 1 exit status : $?
) &
(
( echo start process 2...; sleep 2; echo end process 2; exit 2 ) &
wait $!
echo process 2 exit status : $?
) &
wait
echo main end ...
########### output ###########
main start ...
start process 1...
start process 2...
end process 2
process 2 exit status : 2
end process 1
process 1 exit status : 1
main end ...
위의 예에서 보는 것과 같이 여러개의 background job 을 생성하면 각 프로세스의 종료 상태 값을 main 프로세스에서 알 수가 없습니다. 그럴땐 다음과 같은 방법을 사용해볼 수 있습니다.
#!/bin/bash
trap 'rm -f $tmpfile' EXIT
tmpfile=`mktemp`
number_of_jobs=10
do_job() {
echo start job $i...
sleep $((RANDOM % 5))
echo ...end job $i
exit $((RANDOM % number_of_jobs))
}
for i in $( seq $number_of_jobs ); do
(
do_job &
wait $!
echo job$i : exit status : $? >> $tmpfile
) &
done
wait
i=0
while read -r res; do
echo "$res"
let i++
done < $tmpfile
echo $i jobs done !!!
Job control 관련 키
Ctrl-c
interrupt 신호 ( SIGINT ) 를 foreground job 에 보내 종료시킵니다.
Ctrl-z
suspend 신호 ( SIGTSTP ) 를 foreground job 에 보내 suspend 시키고 background 에 있던 shell 프로세스를 foreground 로 하여 명령을 입력받을 수 있게 합니다.
Input and Output
Input
입력은 foreground job 에서만 받을 수 있습니다. background job 에서 입력을 받게되면 SIGTTIN 신호가 전달되어 suspend 됩니다.
Output
출력은 기본적으로 현재 session 에서 실행되고 있는 모든 job 들이 공유합니다. 그러므로 background job 을 실행할때 제대로 redirection 처리를 하지 않으면 터미널로 출력되는 메시지들이 서로 섞이게 됩니다.
stty tostop
명령을 사용하면 background job 에서 출력이 발생할시 suspend 시킬 수 있습니다.
Background job 은 subshell 에서 실행됩니다.
{ ;}
와 ( )
를 이용한 명령그룹을 background 로 실행 시키면
둘 다 동일하게 subshell 에서 실행됩니다. 그러므로 { ;}
를 이용해 정의한 shell 함수를
background 로 실행 시킬때도 subshell 에서 실행되게 됩니다.
$ AA=100; echo $$ $BASHPID;
31653 31653
$ { AA=200; echo $$ $BASHPID ;} &
31653 9203
$ echo $AA
100
Script 파일 실행 중에 background 로 실행
스크립트 파일을 실행 중에 background 로 명령을 실행하게 되면 이때 실행되는 명령은 job table 에 나타나지 않고 stdin 은 /dev/null 에 연결됩니다. parent process 에 해당하는 스크립트 파일이 먼저 종료하게 되면 PPID 가 init 으로 바뀌어 실행을 지속하므로 데몬 프로세스를 만드는 방법으로도 사용됩니다.
Script 파일 에서는 job control 이 기본적으로 disable 됩니다.
Non-interactive shell 인 script 실행 시에는 기본적으로 job control 이 disable 됩니다.
그렇다고 해서 &
메타 문자를 이용해 background process 를 생성하지 못한다는 것은 아니고
다만 bg, fg, suspend 명령을 사용할 수 없습니다.
하지만 jobs, wait, disown 명령들은 사용할 수 있습니다.
필요에 따라 set -o monitor
옵션 설정을 통해 enable 할 수도 있습니다.
Shell 이 종료되면 background job 들은 어떻게 될까?
프롬프트 상에서 exit 이나 logout 명령으로 종료할 경우
background job 은 두가지 상태를 가집니다. stopped 와 running 인데요. shell 을 exit 할때 stopped 상태에 있는 job 이 있으면 메시지를 통해 프롬프트 상에 알려줍니다. 하지만 stopped job 을 처리하지 않고 다시 exit 명령을 하게되면 shell 이 종료하는데 이때 stopped job 도 함께 종료됩니다.
running 상태에 있는 job 은 기본적으로 shell 이 종료되어도 background 로 실행을 계속합니다. 그러나 바뀌는게 하나 있는데요, 바로 parent process id (ppid) 입니다. background job 들은 shell 에서 실행이 됐기 때문에 ppid 가 shell 이 되는데요. shell 이 종료가 됐기때문에 ppid 가 1 번 그러니까 init process 로 바뀌게 됩니다.
login shell 일경우
shopt -s huponexit
옵션을 설정하게 되면 logout 시에 모든 running background job 들이 HUP 신호를 받고 종료하게 됩니다.
윈도우 상에서 터미널 프로그램을 종료시키거나 시스템이 종료될 경우
remote login 에서 넷트웍, 모뎀 연결이 끊기거나, interactive shell 에
kill -HUP
신호를 주는 경우도 해당되며 이때는 shell 의 stopped, running job 들이 모두 HUP 신호를 받고 종료합니다.