Keyword Commands

time

time [-p] pipeline

명령을 실행하는데 걸린 시간을 표시해줍니다. 명령들이 파이프로 연결되거나 { }, ( ) 로 group 되어있을 경우 모든 명령들을 포함합니다. TIMEFORMAT 환경변수를 이용해 출력 포멧을 변경할수 있습니다.

  • real

wall clock 시간이라고 하며 프로그램이 시작된 후부터 종료될 때까지를 시계로 잰것과 같습니다. 그러므로 프로그램 실행시 단순히 I/O 를 위해 wait 한 시간이나 sleep 한 시간도 모두 포함됩니다.

$ time { sleep 1; sleep 2 ;}

real    0m3.002s
user    0m0.000s
sys     0m0.000s
  • user

user 모드에서 사용한 cpu 시간입니다. wait 한 시간은 포함되지 않습니다. user + sys 시간이 실질적으로 프로그램이 사용한 cpu 시간이라고 볼 수 있습니다.

  • sys

kernel 모드에서 사용한 cpu 시간입니다. 메모리를 할당하거나, 디스크 같은 장치에 접근하는 것은 system call 을 통해서 kernel 모드에서 실행됩니다.

real ≠ user + sys

요즘은 대부분 multi-core cpu 를 사용합니다. 그래서 가령 2 core cpu 를 사용 중이라고 가정했을때, 프로그램이 실행후 종료될때까지 1분이 걸렸고, 각 core 에서 1분의 cpu 시간을 사용했다면 real 은 1분이지만 user + sys 는 2 분이 될수있습니다.

real    1m47.363s
user    2m41.318s
sys     0m4.013s
Exit Status:

종료 상태 값은 pipeline 의 리턴값이 됩니다.

coproc

coproc [NAME] command [redirections]

coproc (coprocess) 는 두 프로세스 간에 양방향 통신을 가능하게 합니다. coproc 는 command 를 background 로 실행시키는데 이때 명령의 stdin, stdout 을 파이프를 통해 FD 에 연결해서 외부로 부터 데이터 입력을 받고 연산결과를 출력할 수 있습니다. 설정된 FD 는 $COPROC array 변수에 원소로 등록되므로 프로세스로 데이터를 보내기 위해서는 >& ${COPROC[1]} , 받기 위해서는 <& ${COPROC[0]} 을 이용할 수 있습니다. background pid 는 $COPROC_PID 변수에 저장됩니다.

# background 로 실행됨
$ coproc  while read -r line; do eval expr "$line"; done
[1] 18008

$ echo "1 + 2" >& ${COPROC[1]}

$ read -r res <& ${COPROC[0]}

$ echo $res
3

$ kill $COPROC_PID

위의 명령은 다음과 동일하다고 볼 수 있습니다. 그러니까 coproc 는 파이프를 생성해서 FD 를 연결하고 삭제하는 작업을 자동으로 해준다고 보면 되겠습니다.

$ mkfifo inpipe outpipe
$ exec 3<>inpipe 4<>outpipe 

$ while read -r line; do eval expr "$line" > inpipe ; done < outpipe  &
[1] 17647

$ echo "1 + 2" >& 4

$ read -r res <& 3

$ echo $res
3

$ kill 17647

$ exec 3>&- 4>&-
$ rm -f inpipe outpipe

다음은 coproc 를 실행했을때 FD 상태입니다. 프로세스 별로 2 개의 FD 가 사용되고 있고, pipe 도 2 개가( 빨강, 파랑 ) 사용되고 있는 것을 볼 수 있습니다.

coproc

coproc 에 의해 생성되는 FD 는 subshell 에서 사용할 수 없습니다.( Process Substitution 은 제외 )

coproc 를 이용한 websocket server

사용방법은 스크립트를 실행한 후에 브라우저를 이용해 http://<Host>:6655/ 에 접속한 후 shell 명령을 입력하면 됩니다. 사용 포트는 브라우저 접속용으로 6655 번을, websocket 용으로 6656 번을 사용합니다. 종료는 동일하게 exit 으로 합니다.

버퍼 크기를 넘어서는 데이터가 출력될 경우 버퍼가 꽉 찬 상태에서 writer 는 더 이상 쓰기를 진행하지 못하고 reader 는 writer 가 쓰기를 완료하지 않아 읽지 못하는 deadlock 이 발생할 수 있습니다.

#!/bin/bash
#####################################
#                                   #
#          WebSocket shell          #
#                                   #
#####################################
# Requires bash 4.x, openssl.
# Author: rootshell@corelogics.de

# 스크립트와 같은 process group 에 속하는 프로세스들에게 TERM 신호를 보냅니다.
trap 'kill 0' EXIT

# 실제 websocket 이 연결되어 입,출력이 일어나는 부분입니다.
# ${d[0]} 가 nc 명령의 stdin 에 연결되고 ${d[1]} 가 stdout 에 연결됩니다.
coproc d { nc -l 6656 ;}

# 6655 포트를 리스닝을 하고 있다가 브라우저가 접속하면 페이지를 전송합니다.
nc -N -l 6655 > /dev/null <<\ENDOFPAGE
HTTP/1.1 200 OK

<html>
<head>
    <script language="javascript">
        var url = location.hostname + ':' + (parseInt(location.port) + 1);
        var ws = new WebSocket('ws://' + url + '/test');
        ws.onmessage = function (msg) {
            document.f.out.value += msg.data + '\n';
            document.f.out.scrollTop = document.f.out.scrollHeight;
        }
        ws.onclose = function () { alert('Connection closed.'); }
        function send() {
            ws.send('' + document.f.in.value);
            document.f.in.value = '';
        }
    </script>
</head>

<body>
    <form name="f">
        <textarea name="out" cols="100" rows="25"></textarea>
        <br>
        Command: <input value="" type="text" size="90" id="in"
            onkeypress="if ( event.keyCode == 13 ) { send(); return false; }" />
    </form>
</body>
</html>
ENDOFPAGE

# 전송된 페이지의 javascript 이 실행되어 websocket 이 연결되는 부분입니다.

until read -r line
    line=$(echo "$line" | tr -d '\r\n')
    test -z "$line"
do
    [ "${line:0:18}" = "Sec-WebSocket-Key:"     ] && key=${line:19}
    [ "${line:0:22}" = "Sec-WebSocket-Version:" ] && ver=$line
done <& "${d[0]}"

rkey=$( echo -n ${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11 |
    openssl dgst -sha1 -binary |
    base64 )

{
    echo -ne "HTTP/1.1 101 Switching Protocols\r\n"
    echo -ne "Upgrade: websocket\r\n"
    echo -ne "Connection: Upgrade\r\n"
    echo -ne "Sec-WebSocket-Accept: ${rkey}\r\n"
    echo -ne "${ver}\r\n\r\n"
} >& "${d[1]}"

# 사용자가 입력한 명령을 실행하여 결과를 ${d[1]} 로 출력하는 함수입니다.
do_cmd() {
    { echo -e ">>> $1" ; eval "$( echo -e "$1")" ;} 2>&1 |
    while read -r line; do
        len=$( echo -n "$line" | wc -c )
        if [ $len -le 125 ]; then 
            echo -ne "\x81\x$( printf '%02x' $len )"
        elif [ $len -le 65535 ]; then
            echo -ne "\x81\x7e\x$( printf '%04x' $len | 
            sed -r 's#([[:xdigit:]]{2})([[:xdigit:]]{2})#\1\\x\2#' )"
        else
            echo -ne "\x81\x7f\x$( printf '%016x' $len | 
            sed -r ':X s#([[:xdigit:]]{2})([[:xdigit:]]{2})#\1\\x\2; tX#' )"
        fi
        echo -n "$line"
    done 
} >& "${d[1]}"

# 이제부터 stdin 입력은 ${d[0]} 로 부터 받습니다.
exec <& "${d[0]}"

# while loop 를 이용해 ${d[0]} 로 부터 사용자 입력을 받아 do_cmd() 함수를 호출합니다.
while true; do
    reclen=$(( `od -An -j1 -N1 -tdI` - 128 ))
    for i in `seq 0 3`; do 
        mk[i]=$( od -An -N1 -tdI )
    done

    cmd=""
    for i in `seq 0 $((reclen - 1))`; do
        bt=$( od -An -N1 -tdI )
        bt=$(( bt ^ ${mk[ $((i % 4)) ]} ))
        cmd+='\x'$(printf '%02x' $bt)
    done

    [ exit = "$( echo -e "$cmd" )" ] && exit

    do_cmd "$cmd"
done