스마트싱스 모드 커스터마이징 하기

스마트싱스를 처음 세팅하면 귀가모드, 외출모드, 취침모드라고 이름붙은 Routines가 있는데 이걸 이용해서 상황별로 자동실행을 시킬 수 있다.

그런데 각 Routine에는 자동실행조건이 있어서 이를 활용하면 State Machine(상태기계)처럼 세팅할 수 있다.

State Machine으로 설계하게 되면 모든 상태변화에 대해서 반응할 수 있게 된다.

더 보기 “스마트싱스 모드 커스터마이징 하기”

PIPE #3/3

다시 EOF를 처리하기 전으로 돌아가보자.

while True:
    name = input()
    print("Hello "+ name)

이걸 실행하면 아래와 같은 에러메세지가 출력된다는 것은 알 것이다.

$ echo "KHS" | python3 hello_multi.py
Hello KHS
Traceback (most recent call last):
  File "hello_multi.py", line 2, in <module>
    name = input()
EOFError: EOF when reading a line
$

출력된 결과를 기념삼아 파일로 바로 저장을 해보자.
표준출력을 파일로 담기 위해서는 > 리디렉션을 이용한다.

$ echo "KHS" | python3 p.py > out.txt
Traceback (most recent call last):
  File "hello_multi.py", line 2, in <module>
    name = input()
EOFError: EOF when reading a line
$

어? 에러메세지는 그냥 출력된다.

그럼 out.txt에는 뭐가 들어있나?

$ cat out.txt
Hello KHS
$

이럴수가 저게 뭔지 알고 내가 print()한 것만 저장을 했을까?

STDERR

여기서 >의 효능을 알아봐야한다. >는 STDOUT을 받아 다른 경로로 쓰는 명령이다.
에러메세지는 STDOUT이 아니라 STDERR로 출력되었기 때문에 화면으로 출력된 것이다.
|의 효능도 STDOUT을 STDIN으로 넘기는 것이지 STDERR을 넘기지 않는다!

STDOUT과 STDERR은 둘다 콘솔로는 출력되지만, 실제로는 서로 다른 레이어라고 생각하면 될 것 같다.

왜 출력방법을 두개로 뒀을까? STDOUT은 프로그램이 입력값에 의해 생산하는 의미 있는 데이터를 출력하는 용도이고,
STDERR은 디버깅과 운영을 위해서 필요한 진행정보를 출력하는 용도이다.

파이프로만 프로그램을 연결해서 쓰다보면 다음 파이프로 넘기면 안되지만 사람은 봐야하는 정보가 있기 마련이다.

막간 리디렉션 활용

>1>의 약자이다. 1은 STDOUT을 의미하고 2는 STDERR을 의미한다.

STDOUT과 STDERR을 서로 다른 파일에 저장하기

$ echo KHS | python3 hello_multi.py 1>out.txt 2>err.txt
$ cat out.txt
Hello KHS
$ cat err.txt
Traceback (most recent call last):
  File "hello_multi.py", line 2, in <module>
    name = input()
EOFError: EOF when reading a line
$

위와 같이 하면 STDOUT은 out.txt에 STDERR은 err.txt에 저장된다.
1과 2의 순서가 바뀌면 안되니 조심.

STDOUT과 STDERR을 한 파일에 같이 저장하기

$ echo KHS | python3 hello_multi.py 1>out.txt 2>&1
$ cat out.txt
Hello KHS
Traceback (most recent call last):
  File "hello_multi.py", line 2, in <module>
    name = input()
EOFError: EOF when reading a line
$

위와 같이하면 STDERR을 STDIN에다가 쓰는 거고 STDIN은 out.txt에 쓰는 것이 되어서,
첨에 생각했던대로 out.txt에 에러메세지까지 모두 저장된다.

crontab에서 자주 보는 구문

0 * * * * /where/is/script/run.sh >/dev/null 2>&1

crontab은 출력결과를 관리자 메일로 발송하므로 메세지를 전부 /dev/null로 보내서 소각(?)하면 조용히~ 실행한다.

다시 돌아와서

파이썬안에서 STDERR로 출력을 해보자.

print()와 sys.stderr 을 활용하는 방법 두가지가 있다.

print()

import sys
print("Hello Error", file=sys.stderr)

sys.stderr

import sys
sys.stderr.write("Hello Error\n")

확인 방법은 >를 써서 실행해보자.

$ python3 hello_error.py 1>out.txt 2>err.txt
$ cat out.txt
$ cat err.txt
Hello Error
$

파이프 끝~

STDIN, STDOUT, STDERR, EOF, 1>, 2>, | 가 무엇인지 한번 더 떠올려보자.

PIPE #2/3

지난 글에서는 한줄의 입력을 처리하는 hello.py 를 만들어 보았다.

여러줄이 들어올 땐 어떻게 할까?

쉽다. 여러번 읽으면 된다.

name = input()
print("Hello "+ name)

조금 깔끔한 출력을 위해 질문을 출력하는 print(“name?”)을 제거했다.

여기까지는 다를게 없고,

while True:
    name = input()
    print("Hello "+ name)

while 문을 추가해보았다.
실행해보면 한도 끝도없이 계속 질문을 한다. ctrl+c 를 눌러서 그만두자.

$ python3 hello_multi.py
KHS
Hello KHS
ABC
Hello ABC
123
Hello 123
^CTraceback (most recent call last):
  File "hello_multi.py", line 2, in <module>
    name = input()
KeyboardInterrupt
$

이 상태에서 echo와 파이프로 연결해보자.

$ echo "KHS" | python3 hello_multi.py
Hello KHS
Traceback (most recent call last):
  File "hello_multi.py", line 2, in <module>
    name = input()
EOFError: EOF when reading a line
$

첫 줄에서 KHS에 대한 반응이 Hello KHS 로 잘 나왔다.
그런데 오류가 났다.
잘보니 ctrl+c 를 눌렀을때와 같은 위치에서(입력을 기다리는 부분) 에러가 조금 다르다.

EOFError이고 오류내용은 “줄을 읽으려는데 EOF예요”

EOF

EOF는 End Of File의 약자인데, 파일의 끝이란 이야기다.

“파일이 무슨 상관이지?” 싶겠지만…

파일을 열어서 한줄씩 읽다보면 결국 끝이 오게 되고, 거기까지가 그 파일의 전체내용이다.

한줄씩 읽는 입장에서는 파일을 읽다가 마지막까지 가던,
표준입력을 읽다가 더이상 입력이 없던,
둘다 EOF인 것이다.

언어에 따라다르지만 표준입출력을 제공하는 언어는 isEOF() 라던지, null이 리턴되던지 해서 EOF를 알리는 방법을 제공하고 있다. 파이썬에서 input()함수는 EOFError를 던짐으로써 그 방법을 제공한다.

자 이제 에러가 안나게 해보자. 정확히 말하자면 EOF를 처리하는 것이다.

while True:
    try:
        name = input()
    except EOFError as eof:
        break
print("Hello "+ name)

EOFError가 잡히면 그냥 루프를 탈출하게 했다.
다시 해보자.

$ echo "KHS" | python3 hello_multi.py
Hello KHS
$

에러가 나지 않는다.

이제 진짜 여러 입력을 넣어보자.

한줄에 이름이 하나씩 쓰여있는 파일을 만들고 cat names.txt 를 파이프로 연결하면 된다.

KHS
sng2c
홍길동

cat 하면 파일의 내용이 표준출력으로 나오고,

$ cat names.txt
KHS
sng2c
홍길동
$

파이프로 연결하면, 여러줄의 입력이 하나씩 처리가 되면서

$ cat names.txt | python3 hello_multi.py
Hello KHS
Hello sng2c
Hello 홍길동
$

아름다운 결과를 볼 수 있다.

뽀나스

홍길동만 뽑아보겠다.

$ cat names.txt | python3 hello_multi.py | grep 홍길동
Hello sng2c
$

뽀나스2

키보드로 EOF를 넣어보자. 그냥 실행하면 무한정 물어보는건 알고 있을 것이다.
입력을 기다릴때 ctrl+d 를 눌러보자.

$ python3 hello_multi.py
KHS
Hello KHS
sng2c
Hello sng2c
^d
$

ctrl+c 를 눌렀을 때와 비교해보자.

PIPE #1/3

C, 파이썬, 펄, 루비, 자바등의 범용 프로그래밍 언어들은 표준입출력을 제공한다.

“표준입출력은 많이 들어봤는데, 나는 그거 안써” 라고 하는 분들을 위한 글이다.

프로그램은 시작과 끝이 있기 마련이고, 작동하는 중에 “내가 뭘 하고 있습니다” 라는 표시를 해야 사람이 그걸 보고 판단을 할 수 있게 된다.

물론 기능이 단순한 copy 같은 프로그램은 굳이 안보여줘도 되지만, 복잡한 기능을 가진 프로그램일 수록 지금 뭘하는지 보여주지 않으면 곤란한 상황이 많이 생긴다.

뻔한 얘기는 여기까지 하고.

표준입력 + 표준출력 + 표준에러 = 표준입출력

다르게 말해서 STDIN, STDOUT, STDERR 이다. 코드상에서는 이렇게 접하게 된다.

자바에서는 System.in, System.out, System.err 로 제공하고,
파이썬에서는 sys.stdin, sys.stdout, sys.stderr 로 제공한다.

자바는 Stream 객체이고, 파이썬에서는 file 오브젝트이지만 그건 중요치 않다.

각각이 뭘 주고 받는지 역할을 이해하는 것이 중요하다.

표준이라고 이름 붙인 것은 약속이기에

  • STDIN 은 프로그램의 입력
  • STDOUT 은 프로그램의 출력
  • STDERR 은 프로그램 오류나 경고등을 출력하기로 약속이 되어있다.

강제는 아니나 이를 지켰을 때 다른 프로그램과의 연동이 손쉬워 진다.

연동?

요즘은 주로 웹서비스나 모바일앱같은 GUI를 가진 프로그램을 작성하는 것이 큰 유행이기 때문에, 연동이라 함은

웹서비스 -> RESTful API
모바일앱 -> Custom URI Schema

등으로 연동을 하는 것이 상식일 것이다.

그런데 웹이나 모바일앱이 나오기 훨씬 이전부터 앱간의 연동의 필요성은 있었고,
이를 위해 표준입출력을 만든 것이다.

자주 쓰게 되는 명령어를 보자면…

ps -ef | grep java

ps 명령의 출력을 grep명령의 입력으로 전달( | )하고 있다. 그리고 grep의 출력이 화면에 나타난다.

요런 것이 OS에서 제공하는 앱간의 연동이자, 표준입출력간의 연결이자, IPC의 일종인 파이프 통신이다.
세가지 용어를 확 묶어버리니까 좀 마음이 편해졌다.

물론 프로그램끼리 통신하는 방법에는 파이프통신도 있고,

  • HTTP통신도 있고,
  • RPC도 있고,
  • COM(ActiveX)도 있고,
  • DB를 서로 뒤져보는 무식한 연동도 있고,
  • 공유메모리를 이용하는 방법도 있고,
  • 직접 실행시 인자를 집어넣어서 자식프로세스를 생성하는 것도 있겠다.

파이프통신은 쉽고, 유연하고, 편리하다

파이프 통신의 처리

파이썬으로만 살펴보겠다.

print("name?")
name = input()
print("Hello "+ name)

input 은 표준입력으로 글자를 받아들이는 역할을 한다.
print 는 표준출력으로 글자를 내보내는 역할을 한다.

잠깐, 아까 파이썬에는 sys.stdin 과 sys.stdout 으로 제공된다고 하지 않았나?
그것도 가능하고 이것도 가능하지만, 일단 엄청나게 자주 쓰이다 보니 쓰기 편한게 하나 더 있다고 생각하면 된다.
내부적으로는 같은 작동을 한다.

import sys
sys.stdout.write("name?\n")
name = sys.stdin.readline().rstrip()
sys.stdout.write("Hello "+name+"\n")
$ python3 hello.py
name?
sng2c
Hello sng2c
$ python3 hello_sys.py
name?
sng2c
Hello sng2c
$

나는 어릴때 C언어를 배울 때, 저것부터 시작했다. 표준입출력을 다루는 중요한 단계임에도 이게 얼마나 유용한 것인지 알지못했고, 초라한 프로그램이라고 생각했다.

name? 을 출력하고 멈춰있는 것을 볼수 있는데, 이게 표준입력을 read() 하기 위해 기다리고 있다. 이때에는 키보드를 타이핑하는 것이 표준입력이 된다.

이름을 써주면 “Hello “와 조합한 결과를 다시 표준출력으로 내보내게 된다.

echo 명령과 파이프로 연결해보자

echo 는 단순히 입력한 값을 “표준출력으로 내보낸다”.

$ echo 'KHS'
KHS

그리고 우리가 작성한 hello.py는 표준입력(아까는 키보드)를 한줄 받아서 인사를 하게 되어있다.
echo 의 출력을 | 로 연결을 해주면 hello.py 의 입력이 된다!!

$ echo 'KHS' | python3 hello.py
name?
Hello KHS
$

키보드 입력을 받기 위해 기다리지 않고,
표준입력으로 들어온 echo 의 ‘KHS’ 를 받아서 “Hello KHS”를 출력해냈다

첫번째(?) 파이프 통신을 축하한다.