본문 바로가기
Python/파이썬 프로그래밍 연습

운영 체제와 작업하기

by 가므자 2012. 4. 27.

이 주제에서는 운영 체제의 역할을 살펴보고 파이썬에서 어떻게 운영 체제에 접근할 수 있는지 알아보겠습니다.

그래서 운영체제란 도대체 무엇인가?

대부분의 컴퓨터 사용자는 컴퓨터에 운영 체제가 있다는 것을 압니다. 그것이 윈도우즈이든 리눅스나 MacOS 또는 기타 무엇이든 말이지요. 그러나 그 운영 체제가 무슨 일을 하는지 정확하게 아는 사용자는 별로 없습니다. 이 사실은 대부분의 상업적 운영 체제에 수 많은 프로그램들이 따라와서 더욱 복잡해집니다. 그 프로그램들은 실제로는 운영체제의 일부가 아니지만 없으면 컴퓨터의 효용성이 아주 떨어집니다. 이런 프로그램의 예로는 이미지 뷰어와 웹 브라우저 그리고 텍스트 편집기 등등이 있습니다. 그래서 운영 체제는 정확하게 무슨 일을 하고 왜 필요한가?

레이어 케이크 원리

그 해답은 컴퓨터가 구성된 방식에 있습니다. 컴퓨터를 바닥에 하드웨어, 즉 전자장치가 깔린 레이어 케이크라고 생각할 수 있습니다. 하드웨어에는 중앙 처리 장치(CPU)와 하드 디스크, 메모리, /출력 서브시스템 (보통 약자로IO)이 포함됩니다. IO에는 직렬 포트와 병령포트, USB 포트, 네트워크 접속 등등이 포함됩니다.

다음 레이어는 BIOS(Basic Input Output System)입니다. BIOS는 소프트웨어의 첫 번째 레이어이며 컴퓨터를 기동시키는 책임을 지고 하드웨어에 대하여 아주 거친 인터페이스를 제공합니다. 예를 들어 하드 드스크 헤드를 트랙에서 트랙으로 이동시키고 한 트랙 안에서는 섹터에서 섹터로 이동시킬 수 있습니다. 그리고 각 포트에 부착된 하드웨어 데이터 버퍼에 개별적으로 바이트를 읽거나 쓸 수 있습니다. BIOS는 사용자가 아주 잘 알고 있는 파일이나 디렉토리를 전혀 모르며 기타 높은 수준의 개념들을 전혀 모릅니다. 오로지 아는 것은 컴퓨터를 구성하는 기본 전자 장치를 다루는 법입니다. 실제로 어떤 경우는 하드웨어 벤더가 BIOS 안의 중요한 위치에 직접 자사의 소프트웨어를 설치하도록 허용해 주기도 합니다 - 그래서 그래픽 카드의 경우 벤더가 BIOS 코드 안의 표준 위치에 자사의 드라이버 위치를 알려주고 BIOS는 그냥 합의된 인터페이스를 호출만 하면 되지만 그 벤더는 자신이 직접 재단한 소프트웨어를 제공합니다.

BIOS 위의 다음 레이어는 운영 체제를 순수하게 만드는 곳입니다. 이 레이어의 구조는 운영 체제에 많이 의존하지만 일반적으로 커널을 구성합니다. , 관련 디바이스 드라이버 핵심 서비스 세트를 구성합니다. 디바이스 드라이버는 커널 안에 구축되어 들어가거나 또는 필요할 때마다 커널이 적재하는 모듈이 될 수도 있습니다 - 파이썬의 모듈이 필요할 때마다 프로그램에 의하여 적재되는 것과 아주 비슷합니다. 레이어가 하는 일은 낮은-수준의 하드웨어를 파일과 폴더 같이 인간이 인식하고 사용하는 논리적 구조로 번역하는 것입니다.

예를 들어 파이썬에서 파일을 열어 읽을 때 무슨 일이 일어나는지 생각해 보세요:

·         파이썬의 open()을 호출한다.

·         파이썬은 운영 체제 함수를 호출하여 같은 파일을 연다

·         운영 체제는 내부 데이터를 찾아서 그 파일이름을 하드 디스크에 있는 트랙과 섹터의 집합으로 번역한다 (실제로 어느 하드 디스크인지 알아낸다!)

·         운영 체제는 다음으로 여러 BIOS 함수를 호출해서 올바른 위치에 헤드를 놓도록 지시한다.

·         운영 체제는 file.read()를 호출한다

·         파이썬은 운영 체제의 read 함수를 호출한다

·         운영 체제는 BIOS에게 그 위치에서 정확하게 바이트 개수를 읽도록 지시한다.

·         운영 체제는 파일에 요구되는 데이터를 모두 읽기 위해 필요한 만큼 BIOS 위치찾기/읽기 단계를 반복한다.

케이크의 마지막 레이어는 (shell)입니다. 쉘은 사용자 환경입니다. 현대의 운영 체제에서 쉘은 보통 그래픽 사용자(Graphical User Interface)로 표현됩니다.

좀 복잡하게 들리셨다면 사실 그렇기 때문입니다! 운영 체제가 있는 이유는 불쌍한 프로그래머가 그에 관하여 생각할 필요가 없도록 도움을 주기 위한 것입니다. 우리는 그냥 open()read()를 호출하기만 하면 됩니다.

프로세스 제어

그렇지만 운영 체제는 단순히 하드웨어에 접근을 제어하는 일 이상을 합니다. 프로그램을 기동시킬 수도 있습니다. 실제로 컴퓨터에서 병행적으로 실행되는 모든 프로그램을 관리하기 위한 메커니즘을 제공합니다. 이렇게 할 필요가 있는 이유는 보통 프로그램의 수보다 CPU가 많이 모자라기 때문입니다. 이 모든 프로그램이 동시에 실행되는 것처럼 보이기 위하여, 일어나는 일은 운영 체제가 각 프로그램에게 CPU를 공유하도록 그들 사이를 아주 빠르게 전환하기 때문입니다 - 이는 시간-배분(time-slicing)이라고 부릅니다. 어떤 운영 체제는 특히 더 뛰어나서, 예를 들어 초기의 윈도우즈와 MacOS 운영 체제는 실행중인 프로그램들과 협력하여 이런 식으로만 다중-작업(multi-task)을 할 수 있습니다. 에러있는 프로그램이 제대로 정지점을 제공하는데 실패하면 컴퓨터는 마치 멈춘 듯이 보입니다!

대부분의 현대 운영 체제는 선점형 멀태태스킹이라고 부르는 시스템을 사용합니다. 이 시스템에서는 프로그램이 무슨 일을 하고 있든지 상관없이 운영체제가 가로채어 자동으로 다음 프로그램에게 CPU에 접근할 권한을 줍니다. 운영 체제에 의존하여 이 프로세스의 효율성을 개선하기 위하여 다양한 알고리즘이 사용됩니다. 흔한 예는 - 라운드 로빈(round robin) 알고리즘, MRU(most recently used), LRU(least recently used), 기타 등등이 있습니다. 역시, 프로그래머의 관점에서 보통 그냥 이 모든 것을 무시하고 사실상 여러 병렬 프로그램이 실행되고 있다고 간주하면 됩니다.

사용자 접근과 보안

언급하고 싶은 운영 체제의 마지막 측면은 사용자를 통제하는 것입니다. 현대의 컴퓨터는 대부분 적어도 여러 다양한 사용자가 머신에 접근할 수 있도록 허용해 줍니다. 각 사용자마다 독자적으로 파일과 데스크 탑 등등을 설정합니다. 여러 운영 체제는 한 발 더 나아가 여러 사용자가 동시에 로그인할 수 있도록 허용합니다. 이를 보통 멀티-세션라고 부릅니다. 그렇지만, 사용자가 많으면 보안의 문제가 발생합니다. 본인이 여러분의 데이터를 보면 안되며 그 반대도 안되는 것이 중요합니다. 운영 체제는 각 사용자의 파일이 확실하게 보호되고 적절한 권한을 지닌 자만 데이터에 접근할 수 있도록 보증하는 책임을 집니다..

그래서 어떻게 사용할 있는가?

운영 체제의 임무는 이 모든 상세를 싹 걷어서 없애는 것이기 때문에 학문적 호기심이 아니라면 왜 프로그래머가 관심을 두어야 하는지 궁금하실 겁니다. 그 대답은 표준 프로그래밍 함수로는 불가능한 방식으로 가끔 하드웨어와 상호대화할 필요가 있기 때문입니다. 어쩌면 또다른 프로그램을 자신의 프로그램 안에서 기동시킬 필요도 있을 수 있습니다. 어떤 경우는 사용자가 프로그램 안에서 컴퓨터를 제어하고 싶듯이 컴퓨터를 제어하고 싶을 때도 있습니다. 이런 일을 하려면 아래에 깔린 운영 체제의 편의 기능에 접근할 필요가 있습니다.

파이썬은 운영 체제와 상호작용하는데 필요한 모듈을 많이 제공합니다. 가장 중요한 모듈은 os 입니다. 이 모듈은 뚜껑 아래에서 낮은 수준의 다른 모듈을 적재함으로써 모든 운영 체제에 공통적인 인터페이스를 제공합니다. 결론적으로 os 모듈에 정의된 함수를 호출할 수 있지만 어떤 운영 체제는 내부적으로 그런 함수를 구현한 방식에 따라 약간 다르게 행동합니다. 이는 보통 문제가 되지 않지만, os 함수에서 이상한 행위를 마주한다면 문서에서 혹시라도 여러분의 운영 체제 구현에 제한이 있는지 알아 보세요.

다음으로 살펴볼 다른 운영 체제의 모듈은 shutil입니다. 이 모듈을 사용하면 프로그래머는 파일과 폴더를 사용자 수준에서 제어할 수 있습니다. os.pathglob도 모두 컴퓨터 파일 시스템을 돌아보는데 필요한 편의기능을 제공합니다. 실제로 제일 먼저 그 부분을 살펴보겠습니다.

파일 다루기

이 자습서에서 전에 이미 파일 다루기를 살펴보았습니다. 그래서 운영 체제는 우리가 할 수 없는 것을 어떻게 도울 수 있을까? 한 가지 예를 들면 파일을 지울 수 있습니다. 표준 파일 메쏘드로 파일을 만들고 수정할 수 있지만 지울 수는 없습니다. 또 파일을 검색할 수 있습니다. 파일이 어디에 있는지 안다면 open()이 좋지만 모른다면 어떻게 찾을 수 있는가? 그 생각을 확장해서 파일 그룹을 처리하려면 어떻게 하는가 - 한 폴더에 있는 모든 이미지 파일을 조작하고 싶다고 해 봅시다. 그리고 마지막으로 파일에서 읽은 것들을 더 세심하게 제어하려면 어떻게 하는가? 표준 메쏘드는 한 줄 또는 전체 파일을 읽지만, 단지 몇 바이트만 필요하면 어떻게 하는가? 이 모든 것들을 OS 함수로 할 수 있습니다.

파일 찾기

파일 찾기를 위하여 제일 먼저 살펴보고 싶은 모듈은 glob 모듈입니다. 이 모듈은 파일이름 리스트를 얻는데 사용됩니다. 기괴한 이름은 유닉스로부터 왔습니다. 유닉스 세계에서는 와일드카드 문자를 사용하여 파일 그룹을 선택하는 행위를 기술하는데 오랫동안 그 용어가 사용되어 왔습니다. 어디에서 기원했는지는 잘 모르겠습니다. 혹시 아시는 분이 있다면 이메일 한통 부탁드립니다!

모듈 그 자체는 아주 사용하기 쉽습니다. 반입하면 (당연!) glob()이라는 함수만 하나 달랑 있다는 것을 알 수 있습니다! 부합시킬 패턴을 건네면 함수는 부합하는 파일이름을 모두 리스트에 담아 돌려줍니다 - 이 보다 더 쉬울 수 있을까요? 다음은 예입니다:

import glob

files = glob.glob("*.exe")

print files

현재 디렉토리에 있는 실행 파일의 목록을 얻습니다. 당연히 이런 의문이 떠 오릅니다. 현재 디렉토리가 무엇인지 어떻게 아는가? 그리고 현재 디렉토리를 바꿀 수 있는가? 물론 바꿀 수 있습니다 - os 모듈을 사용하면 되지요!

import os

print os.getcwd() # cwd = 현재 작업 디렉토리

os.chdir("C:/WINDOWS")

print os.getcwd()

print os.listdir('.')  # 마침내 cwd 목록을 얻는다.

 

경로 이름에 사선(/)을 사용하면 역사선(\\) 피신 문자를 중복해 사용하지 않아도 됨을 주목하세요. 역사선은 보통 윈도우즈와 도스에서 사용됩니다. 사선은 또한 MacOS에도 작동합니다. 그래서 사선은 아주 편리한 범용 경로 가름자로 간주될 수 있습니다! 가름자가 특정 OS에 있는지 알아 보려면 현재 OS 설정정보를 알려주는 os.sep 변수가 있습니다.

그래서 이제 현재 디렉토리에서 파일을 찾는 법과 현재 디렉토리를 마음대로 바꾸는 법을 알았습니다. 그러나 여전히 특정 파일을 찾는 일은 따분한 고생입니다. 그럴 때는 아주 강력한 os.walk() 함수를 사용할 수 있습니다.

시작 지점 아래 어딘가에 위치한 특정 파일을 찾기 위하여 os.walk를 사용하는 예를 살펴보겠습니다. 프로그램에서 사용할 수 있도록 findfile 함수를 만들어 보겠습니다.

먼저 테스트 환경을 만듭니다. 루트 디렉토리 아래에 폴더를 계통적으로 구성합니다. 폴더마다 파일 몇 개를 배치하고 그중 한 폴더에 검색하고 싶은 파일을 두었습니다. 그 파일 이름은 target.txt입니다. 이 구조는 다음 찰칵 그림에서 보실 수 있습니다:

explorer view

os.walk 함수는 시작 지점을 매개변수로 받고 발생자(generator)를 돌려줍니다 (발생자는 요구되는 만큼만 자신을 구축하는 일종의 가상 리스트입니다). 이 발생자는 (종종 3-터플이라고 불리우는) 원소 3개의 터플로 구성됩니다. 루트와 현재 루트에 있는 디렉토리 리스트 그리고 현재 파일 리스트가 그 원소입니다. 본인이 만든 계통도를 보면 첫 터플은 다음과 같이 보일 것이라고 예상할 수 있습니다:

( 'Root', ['D1','D2','D3'], ['FA.txt','FB.txt'])

상호대화 프롬프트에서 for 회돌이를 작성해 보면 쉽게 점검할 수 있습니다:

>>> for t in os.walk('Root'):

...    print t

...

('Root', ['D1', 'D2', 'D3'], ['FA.txt', 'FB.txt'])

('Root/D1', ['D1-1'], ['FC.txt'])

('Root/D1/D1-1', [], ['FF.txt'])

('Root/D2', [], ['FD.txt'])

('Root/D3', ['D3-1'], ['FE.txt'])

('Root/D3/D3-1', [], ['target.txt'])

>>>

os.walk이 취한 경로를 선명하게 보여줍니다. 또 어떻게 파일을 찾아 그 경로를 완전하게 구성할 수 있는지 보여줍니다. os.walk이 돌려준 터플의 파일(files) 원소를 들여다보고 이름을 찾아서 그 이름이 담긴 터플의 루트(root) 값과 이름을 조합하면 됩니다.

정규 표현식을 사용하고 리스트를 돌려주도록 함수를 작성하면 앞서 본 간단한 glob.glob 보다 훨씬 더 강력한 (그러나 더 느린!) 함수를 만들 수 있습니다. 시험삼아 한 번 해봅시다. 다음과 같이 보일 것입니다:

 

# findfile.py 모듈. findfile()이라는 함수 하나만 있으며,

# os.walk()을 이용한다.

 

import os,re

 

def findfile(filepattern, base = '.'):

    regex = re.compile(filepattern)

    matches = []

    for root,dirs,files in os.walk(base):

        for f in files:

          if regex.match(f):

             matches.append(root + '/' + f)

    return matches

다음과 같이 상호대화 프롬프트에서 테스트해 볼 수 있습니다:

>>> import findfile

>>> findfile.findfile('t.*','Root')

['Root/D3/D3-1/target.txt']

>>> findfile.findfile('F.*','Root')

['Root/FA.txt', 'Root/FB.txt', 'Root/D1/FC.txt', 'Root/D1/D1-1/FF.txt', 'Root/D2

/FD.txt', 'Root/D3/FE.txt']

>>> findfile.findfile('.*\.txt','Root')

['Root/FA.txt', 'Root/FB.txt', 'Root/D1/FC.txt', 'Root/D1/D1-1/FF.txt', 'Root/D2

/FD.txt', 'Root/D3/FE.txt', 'Root/D3/D3-1/target.txt']

>>> findfile.findfile('D.*','Root')

[]

잘 작동하는군요. 지난 예제에서는 파일에만 작동한다는 것을 주목하세요. 디렉토리 이름은 dirs 리스트 안에 있었고 그 안은 들여다보지 않았습니다. 연습삼아 새로운 함수를 findfiles 모듈에 추가해 보세요. 이름은 finddir()이고 주어진 정규 표현식에 부합하는 디렉토리를 찾습니다. 다음 둘을 조합하여 세 번째 함수로 파일과 디렉토리를 모두 찾는 findall()을 만들어 보세요.

파일 이동과 복사 그리고 삭제하기

파일 다루기 주제에서 파일을 복사하는 법을 연구하였습니다. 읽은 다음 새로운 위치에 출력해서 말입니다. 그렇지만 운영 체제에게 이런 작업을 대신 시킬 수 있습니다. 단 하나의 명령어로 말입니다! 파이썬에서는 shutil을 이런 종류의 작업에 사용합니다. shutil 모듈은 여러 유용한 함수가 있지만 살펴 볼 함수들은 다음과 같습니다 (파이썬 모듈 문서를 요약함):

·         copy(src, dst)

src 파일을 dst 파일이나 디렉토리에 복사한다. dst가 디렉토리이면, 지정된 디렉토리에 src와 바탕이름이 같은 파일이 생성된다(즉 오버라이트된다). 허가 비트도 복사된다. srcdst는 문자열로 주어진 경로 이름이다.

·         move(src, dst)

재귀적으로 파일이나 디렉토리를 다른 곳으로 이동시킨다.

목적지가 현재 파일시스템이면, 그냥 src의 이름을 바꾸어라. 그렇지 않으면, srcdst에 복사하고 src를 삭제하라.

좀 이상하지만, 다음 함수들은 shutil이 아니라 os 모듈에 있습니다:

·         remove(path)

파일 경로를 제거한다.

path가 디렉토리이면, OSError가 일어난다; (디렉토리를 제거하려면 rmdir()을 사용하라).

·         rename(src, dst)

src 파일이나 디렉토리 dst로 이름을 바꾼다.

dst가 디렉토리이면, OSError가 일어난다.

이런 함수들이 작동하는 것을 보는 가장 쉬운 방법은 그냥 상호대화 프롬프트에서 시험해 보는 것입니다. 위의 os.walk 예제를 위해 구성한 디렉토리/파일 구조를 사용하세요:

>>> import os

>>> import shutil as sh

>>> import glob as g

>>> os.chdir('Root')

>>> os.listdir('.')

['D1', 'D2', 'D3', 'FA.txt', 'FB.txt']

 

>>> sh.copy('FA.txt', 'CA.txt')

>>> os.listdir('.')

['CA.txt', 'D1', 'D2', 'D3', 'FA.txt', 'FB.txt']

 

>>> sh.move('FB.txt','CB.txt')

>>> os.listdir('.')

['CA.txt', 'CB.txt', 'D1', 'D2', 'D3', 'FA.txt']

 

>>> os.remove('FA.txt')

>>> os.listdir('.')

['CA.txt', 'CB.txt', 'D1', 'D2', 'D3']

 

>>> for f in g.glob('*.txt'):

...    newname = f.replace('C','F')

...    os.rename(f,newname)

...

>>> os.listdir('.')

['D1', 'D2', 'D3', 'FA.txt', 'FB.txt']

>>>

>>>

예제에서 파일을 이동시키고 복사했으며, 남아있는 원래 파일을 삭제한 다음 rename을 사용하여 폴더를 원래 상태로 복원했습니다. 이런 연산은 모두 사용자가 명령어 프롬프트나 파일 브라우저에서 할 수 있는 일이지만, 여기에서는 파이썬을 사용하여 해 보았습니다. 여러 번 바꾸기 위해 for 회돌이를 사용한 것도 주목하세요. 회돌이 안에 온갖 규칙과 점검 방법을 추가해서, 아주 강력한 파일 조작 도구를 만들 가능성을 줄 수 있었습니다. 물론, 코드를 스크립트로 저장하면, 그냥 스크립트를 실행해서 언제든지 원하는대로 이런 변화를 수행할 수 있습니다.

파일 성격 테스트

종종 파일을 다룰 때 해당 파일의 성격에 관하여 좀 알 필요가 있습니다. 예를 들어 glob으로 디렉토리 리스트를 읽을 때, 해당 "파일"이 실제 파일인가 아니면 디렉토리인가? 또한 언제 마지막으로 갱신되었는지 아는 것이, 심지어 주기적으로 갱신되고 있는지 감시하는 것이 유용할 수 있습니다 - 그리하여 다른 사용자나 프로그램이 파일에 접근 중인지 보여주는 겁니다. 비슷하게 파일의 크기를 감시해서 커지고 있는지 알아보고 싶을 수도 있습니다.

프로그램에서 OS의 특징을 이용하면 이 모든 일을 할 수 있습니다. 제일 먼저 어떤 종류의 일을 처리하고 있는지 알아 보는 법을 살펴보겠습니다:

import os.path as p

import glob

for item in glob.glob('*')

   if p.isfile(item): print item, ' is a file'

   elif p.isdir(item): print item, ' is a directory'

   else: print item, ' is of unknown type'

 

테스트 함수들은 os.path 모듈에 있음을 주목하세요. 또한 여러 다른 테스트도 있음을 주목하세요. 이런 테스트는 os.path 모듈 문서에서 읽어 볼 수 있습니다.

살펴볼 파일의 다음 성격은 그의 나이입니다. 파일 생명선에 재미있는 날짜가 많습니다. 그 중에 제일 처음은 생성 날이고, 다음은 최근 수정 날짜 그리고 마지막으로 최근 접근 날짜입니다. 모든 운영체제가 모든 데이터를 저장하지는 않지만 대부분은 생성날짜와 수정날짜를 제공합니다. 파이썬에는 생성날짜와 수정 날짜는 os.path 모듈을 통하여 각각 ctime() 함수와 mtime()을 사용하여 접근할 수 있습니다.

Root 구조에서 파일을 살펴보겠습니다. 거의 모두 같은 시간에 만들어졌지만 최상위 파일은 약간 다릅니다. 앞 예제에서 rename()을 사용하여 조작하였기 때문입니다.

>>> import time as t

>>> os.listdir('.')

>>> for r,d,files in os.walk('.'):

...   for f in files:

...     print f,' created: %s:%s:%s' % t.localtime(p.getctime(r+'/'+f))[3:6]

...     print f,' modified: %s:%s:%s' % t.localtime(p.getmtime(r+'/'+f))[3:6]

...

FA.txt  created: 13:42:11

FA.txt  modified: 13:36:27

FB.txt  created: 13:42:11

FB.txt  modified: 17:32:5

FC.txt  created: 17:32:46

FC.txt  modified: 17:32:5

FF.txt  created: 17:34:3

FF.txt  modified: 17:32:5

FD.txt  created: 17:33:12

FD.txt  modified: 17:32:5

FE.txt  created: 17:33:53

FE.txt  modified: 17:32:5

target.txt  created: 17:34:28

target.txt  modified: 17:32:5

>>>

FAFB에 대한 결과가 약간 이상하지요? 마치 수정되자 마자 생성된 듯 보입니다! 그 이유는 원래 파일을 복사했기 때문입니다. 다음 바로 원래 파일을 지우고 그 사본의 이름을 바꾸었기 때문입니다. OS는 내용이 바뀌지 않았음을 인지하고 있으므로 원래 사본의 시간을 수정 시간으로 보여주지만 이름 바꾸기 연산(rename)을 현재 파일 이름을 생성한 시간으로 간주합니다!

파일에 관하여 필요한 대부분의 정보를 단 하나의 터플에 담아 돌려줄 수 있는 유용한 OS 함수가 있습니다. 이른바 stat() 함수가 바로 그것으로서 여러 변종이 있습니다. 그렇지만 오직 os 모듈에 있는 버전만 살펴보겠습니다.

os.stat()은 다음 내용을 담고 있는 터플을 돌려줍니다:

·         st_mode (보호 비트),

·         st_ino (inode 번호),

·         st_dev (장치),

·         st_nlink (하드 링크 개수),

·         st_uid (사용자 ID),

·         st_gid (그룹 ID),

·         st_size (바이트 단위의 파일 크기),

·         st_atime (최근 접근 시간),

·         st_mtime (최근 수정 시간),

·         st_ctime (생성시간, 그러나 플랫폼에 의존한다)

가끔은 아래의 운영 체제가 지원하는 바에 따라 필드가 더 있을 수도 있음을 주목하세요. 운영체제 문서를 참고하세요.

다음은 최상위 수준 FA.txt 파일에 적용한 간단한 예입니다:

>>> fmtString = "protection: %s\nsize: %s\naccessed: %s\ncreated: %s"

>>> stats = os.stat('FA.txt')

>>> print fmString % stats[0],stats[6],stats[7],stats[9]

protection: 33279

size: 0

access: 1132407387

created: 1132407731

그냥 파일에 있는 바이트 개수일 뿐인 size를 제외하고, 다른 값들은 모두 비트 단위로 디코딩해야 사람이 읽을 수 있음에 주목하세요. 각각을 처리하는 법을 살펴보겠습니다. 타임스탬프(timestamps)는 쉽습니다. 숫자가 그냥 기원(epoch(1970.01.01 00:00:00 UTC)) 이후로 지난 초의 개수이기 때문입니다 - 이 자습서에서 이전에 다룬 바 있습니다l - time 모듈을 사용하여 (위의 localtime()을 한 것처럼) 의미있는 데이터 구조나 문자열로 변환할 수도 있습니다. 보호 포맷은 디코드될 필요가 있으며 디코딩은 stat 모듈에 있는 약간 특수한 값을 사용하여 처리됩니다. 그렇지만 아직 다루지 않은 비트 연산자라는 특수한 연산자도 필요합니다. 아직 이런 연산자를 보지 못했다면 먼저 아래의 박스에 있는 글을 읽어 보세요.

비트 연산자와 플래그

 

stat 모듈에는 미리 정의된 상수(constants) 집합이 있습니다 - 상수는 바꿀 의도가 없는 값을 가진 변수들입니다. 이 상수들에 비트 연산자(bitwise operators)를 사용하면 허가 정보를 알아낼 수 있습니다. 비트 연산자는 이전에 사용해 본 불리언 논리 연산자와 똑같습니다: andor 그리고 not, 또하나 더 새로운 xor이 있습니다. 차이점은 그 이름이 암시하듯이 이런 버전은 데이터의 전체 값이 아니라 이진 비트를 연산합니다.

 

stat 모듈에 있는 값은 이진 값으로 정의된 변수를 살펴보면 알 수 있습니다. 불행하게도 파이썬은 내장 이진 포맷 옵션을 제공하지 않으므로 짧은 변환 함수를 작성할 필요가 합니다. 8진수의 각 자리가 그의 이진 표현으로 직접 변환되며 8진수 변환 함수가 내장되어 있다는 사실을 이용합니다. bin(n) 함수는 다음과 같이 보입니다:

 

def bin(n):

   digits = {'0':'000','1':'001','2':'010','3':'011',

             '4':'100','5':'101','6':'110','7':'111'}

   octStr = "%o" % n

   # 8진 문자열로 변환한다.

   binStr = ''

   # 8진수를 그와 동등한 이진수로 변환한다.

   for c in octStr: binStr += digits[c]

   return binStr

비트 연산자

이제 비트 연산자가 어떻게 작동하는지 살펴보겠습니다. 입력 값과 출력 값을 표시하기 위해 만든 bin 함수를 사용합니다.

먼저 비트별 and 연산자의 효과를 살펴보겠습니다. 심볼은 &입니다

 

>>> print bin(5)

101

>>> print bin(1)

001

>>> print bin(2)

010

>>> print bin(5 & 1)

001

>>> print bin( 5 & 2)

000

 

결과를 살펴보고 무슨 일이 일어나는지 생각해 봅시다. 논리 and 연산자는 두 값이 모두 참일 경우에만 참이라는 사실을 떠올려 보세요. 비슷하게 비트 & 연산자는 상응하는 두 비트가 참 (1)일 경에만 참(1)입니다. 그래서 5 & 1이라면 가장 오른쪽 비트는 두 경우 모두 1이므로 그 결과 가장 오른쪽 비트가 1로 설정됩니다. 5 & 2이라면 두 비트가 모두 1인 위치가 하나도 없으므로 그 결과는 모두 0입니다.

 

이런 행위는 비트 연산이라는 흥미로운 특징으로 이끕니다. 1로 설정된 이진 숫자와 이진 값을 'and' 연산하면 테스트 값에서 상응하는 비트도 1 번 위치에 있다는 것을 알 수 있습니다. 만약 그렇다면, 0-아닌 결과를 돌려 받습니다.

 

예제를 살펴봅시다. 숫자에서 두 번째 비트가 설정되어 있는지 테스트하고 싶다고 가정하겠습니다. 위에서 보았듯이 (오른쪽으로부터 세어서!) 두 번째 위치에 비트가 설정된 값은 2입니다. 테스트를 살펴봅시다:</>

TWOBIT = 2

for n in range(10):

   if n & TWOBIT: print n,' = ',bin(n)

2,3,6 그리고 7 모두 두 번째 비트가 설정되어 있음을 볼 수 있습니다.

 

비트별 OR(|) 연산자와 비트별 NOT(~) 연산자도 비슷하게 할 수 있습니다. (그렇지만 주의하세요. 이 때문에 과도하게 단순한 bin() 함수가 망가질 수도 있거든요!). bin() 함수를 이용하여 비트별로 입력과 출력을 화면에서 보면서 이런 것들을 가지고 놀아 보세요. 다양한 연산자가 작동하는 원리를 보실 수 있을 것입니다. 값을 비트끼리 비교하는 법을 꼭 기억하세요.

 

마지막 비트 연산자는 xor(exclusive or) 연산자입니다. 이 연산자는 심볼이 ^입니다. 배타적 or 연산은 테스트 값중 하나가 참이면 참이지만 둘 모두 참이면 거짓입니다 (역주: 두 값이 같지 않으면 참, 같으면 거짓이라고 기억하는게 좋다). 이 때문에 결과가 흥미롭습니다. 예를 들어 어떤 숫자도 자신과 xor 연산을 하면 언제나 결과가 0입니다! 비슷하게 어떤 숫자를 키와 xor 연산한 결과는 나중에 같은 키로 xor 연산하면 원래 결과를 돌려줍니다! 이는 암호학에서 아주 유용하게 쓰입니다. 몇 가지 예를 살펴보고 나서 모듈과 허가값을 찾는 일로 돌아가 보겠습니다.

 

>>> print bin(5 ^ 2)

111

>>> print bin(5^5)

000

>>> print bin((5^2)^2)

101

플래그

불리언 값이 정보를 저장하는데 사용되면 그 변수는 보통 플래그(flag)라고 부릅니다 - 플래그는 선택되거나 버려지거나 둘 중에 하나이기 때문입니다 (어중간한 것은 무시합니다!). 한 개체에 관련된 그런 값이 많을 경우 보통 숫자를 하나 이용하여 플래그 집합을 저장합니다. 개별 데이터 비트마다 따로따로 플래그를 표현하지요. 이런 플래그 값은 여기에서 연구중인 비트 연산자를 사용하면 열람할 수 있습니다. 특히 비트별로 조합된 디코딩 값을 마스크(mask)라고 부르는데 이를 이용하면 특정한 비트를 추출할 수 있습니다. (TWOBIT 값이 바로 두 번째 비트를 추출하기 위한 마스크였습니다.)

 

stat 모듈은 본질적으로 미리 정의된 마스크 세트로서 os.stat() 함수가 돌려준 허가 플래그를 조사하는데 사용됩니다.

stat 상수를 비트 연산자에 사용하기

이제 stat 값들을 이진 숫자로 살펴보겠습니다. 그리고 어떻게 사용하는지 알아보겠습니다.

>>> import stat

>>> dir(stat)

['ST_ATIME', 'ST_CTIME', 'ST_DEV', 'ST_GID', 'ST_INO', 'ST_MODE', 'ST_MTIME',

'ST_NLINK', 'ST_SIZE', 'ST_UID', 'S_ENFMT', 'S_IEXEC', 'S_IFBLK', 'S_IFCHR',

'S_IFDIR', 'S_IFIFO', 'S_IFLNK', 'S_IFMT', 'S_IFREG', 'S_IFSOCK', 'S_IMODE',

'S_IREAD', 'S_IRGRP', 'S_IROTH', 'S_IRUSR', 'S_IRWXG', 'S_IRWXO', 'S_IRWXU',

'S_ISBLK','S_ISCHR', 'S_ISDIR', 'S_ISFIFO', 'S_ISGID', 'S_ISLNK', 'S_ISREG',

'S_ISSOCK', 'S_ISUID', 'S_ISVTX', 'S_IWGRP', 'S_IWOTH', 'S_IWRITE', 'S_IWUSR',

'S_IXGRP', 'S_IXOTH', 'S_IXUSR', '__builtins__', '__doc__', '__file__',

'__name__']

>>> print bin(stat.S_IREAD)

100000000

>>> print bin(stat.S_IWRITE)

010000000

>>> print bin(stat.S_IEXEC)

001000000

제일 먼저 지적할 것은 수 많은 상수가 정의되어 있다는 것입니다! 다음으로 주목할 것은 인쇄된 세 개의 값이 파일에서 각각 읽기, 쓰기, 실행의 허용여부를 결정하는 값이라는 것입니다. 각 값에 하나의 비트가 설정되어 있음을 주목하세요. 위의 예제의 TWOBIT 값처럼 말입니다. 그래서 비트연산자를 사용하면 파일의 허가권을 알아볼 수 있습니다. 다음과 같이 os.stat() 함수를 호출해 보면!:

import os, stat

perm = os.stat()[]

if perm & stat.S_IREAD:

   print '이 파일은 읽을 수 있습니다'

if perm & stat.S_IWRITE:

   print '이 파일은 쓸 수 있습니다'

if perm & stat.S_IEXEC:

   print '이 파일은 실행할 수 있습니다'

이 정도가 보통 우리의 관심을 끄는 허가권입니다. 그러나 더 알고 싶다면, stat 모듈 문서를 주의깊게 살펴보고 제대로 이해하고 있는지 파이썬 >>> 프롬프트에서 실험해 보세요.

파일의 허가권 바꾸는

파일에 대한 허가권이 현재 어떻게 설정되는지 알았으므로 os 모듈을 사용하여 그런 허가권을 좀 더 적당하게 바꿀 수도 있습니다. 파이썬은 허가권을 바꾸는데 유닉스 관례를 이용합니다. 각 파일에는 세 개의 플래그 집합(read write 그리고 execute)이 있는데 각 플래그에는 세 명의 사용자 범주 (owner group 그리고 world)가 있습니다. 그리하여 파일당 총 9개의 플래그가 있습니다. 이 플래그는 9개의 비트로 표현됩니다. 이 비트들은 os.stat가 돌려주는 허가 플래그의 가장 오른쪽 비트들을 구성합니다.

허가권을 바꾸려면 그냥 비트를 적절하게 설정하면 됩니다. 이렇게 하기 위하여 os 모듈에 chmod()라고 부르는 편의 함수가 있습니다. 이 함수는 인자로 9 비트 숫자를 취합니다. 9 비트 이진 문자열을 실제 숫자로 변환하기 위해 내장 int() 함수를 사용하여 다음과 같이 밑수 2를 나타내는 두 번째 인자를 건넬 수 있습니다:

>>> perm = '111101100' # rwxr--r--

>>> print int(perm,2)

492

이제 이 십진 값을 사용하면 파일의 허가권을 바꿀 수 있습니다.

>>> os.chmod('FA.txt',492)

또는 다음과 같이 단 한 번에 그 모든 걸 다 처리할 수 있습니다:

>>> os.chmod('FA.txt',int('111101100',2))

8진수에 익숙하다면 8진수의 각 자리가 세 개의 이진 비트를 나타낸다는 사실을 알고 계실 것입니다. 그리하여 아주 편리하게 허가권을 세 개의 8진 자리로 표현할 수 있습니다. (위에 작성해 둔 bin 함수에서 그것을 볼 수 있습니다). 일반적인 유닉스 사용자는 이런 식으로 허가권을 표현하는데 익숙합니다. 파이썬에서도 그 방법을 사용할 수 있습니다. 다음과 같이 chmod 함수를 호출하면 됩니다:

>>> # 반드시 앞에 0을 붙여서 8진수로 취급해야 한다.

>>> os.chmod('FA.txt',0754)

위의 예제는 모두 같은 일을 합니다. 소유자의 허가권은 read write 그리고 execute에 설정하는 반면 그룹에는 readexecute에 설정하고 world 권한은 read만 설정합니다.

경로와 파일 그리고 폴더

프로그램을 개발할 때 보통 무엇이나 찾을 수 있게 데이터 파일은 프로그램 파일과 같은 폴더에 둡니다. 앞으로는 일반적으로 사용할 프로그램에서 파일들이 아는 장소에 있다고 간주할 수 없습니다. 그래서 파일을 찾을 필요가 있을 겁니다 - 아마도 위에서 기술한 바와 같이 glob 또는 os.walk를 사용하겠지요.

필요한 파일을 찾았으면 파일을 열고 싶거나 그 정보를 조사하고 싶을 것이고 그렇다면 완전한 경로를 설정할 필요가 있습니다. 대안적으로, 완전한 경로가 주어지면 그 경로를 분석해서 오직 파일 이름만 추출하고 싶을 수도 있고, 또는 폴더 이름만 변수에 보관하고 싶을 수 있습니다. os.path가 바로 그런 일을 하는데 필요한 도구들을 제공합니다.

파이썬은 파일이름이 여러 부분으로 구성된다고 간주합니다. 첫째 선택적인 드라이브 문자가 하나 있습니다 (윈도우즈가 아닌 운영 체제는 종종 파일 이름에 물리적 드라이브의 개념이 없습니다.). 다음에는 폴더 이름이 특별한 문자로 분리되어 연속적으로 따라옵니다 (파이썬에서는 '/'를 사용할 수 있습니다. 그리고 거의 언제나 작동합니다. 그러나 어떤 운영체제는 자신만의 독특한 버릇이 있습니다). 마지막으로 파일이름 즉, 기본이름(basename)이 있습니다. 차례대로 기본이름은 보통 파일 확장자(extension)가 있습니다. 예제를 하나 보겠습니다:

F:/PROJECTS/PYTHON/Root/FA.txt

FA.txt 파일은 Root 폴더에 있습니다. F: 드라이브의 최상위 수준 디렉토리 PROJECTS 폴더가 있고 그 아래에 PYTHON 폴더가 있으며 그 아래에 Root 폴더가 있습니다.

완전한 경로가 주어지면 기본이름이나 확장자 또는 폴더를 추출할 수 있습니다. 다음과 같이 os.path 모듈에 있는 함수를 사용하면 됩니다:

>>> pth = F:/PROJECTS/PYTHON/Root/FA.txt

>>> stem, aFile = os.path.split(pth)

>>> print 'stem : ',stem, ' file = ',aFile

stem :  F:/PROJECTS/PYTHON/Root  file =  FA.txt

 

>>> # 윈도우즈 같이 드라이브 개념이 있는 OS에서만 작동한다.

>>> print os.path.splitdrive(pth)

('F:', '/PROJECTS/PYTHON/Root/FA.txt')

 

>>> print os.path.dirname(pth)

F:/PROJECTS/PYTHON/Root

 

>>> print os.path.basename(pth)

FA.txt

 

>>> print os.path.splitext(aFile)

('FA', '.txt')

그리고 이 모든 것을 다시 결합할 수 있습니다...

>>> print os.path.join(stem,aFile)

F:/PROJECTS/PYTHON/Root/FA.txt

os.path.join에 관하여 기억할 한 가지는 OS에 대하여 공식적인 가름 문자를 사용한다는 것입니다. 그리하여 플랫폼에 걸쳐서 이식성 있는 경로를 구축하고 싶다면 os.path.join를 사용하면 됩니다. 프로그램에 경로를 손수 코딩해 넣는 수고를 하지 마세요.

파일 기술자 파일 객체

어떤 os 계열의 모듈은 파일 접근에 우리가 익숙했던 메커니즘과 약간 다른 메커니즘을 사용합니다. 이를 파일 기술자(file descriptor)라고 부르는데 지금까지 사용해 온 파일 객체보다 운영 체제 개념의 파일에 더 밀접하게 묶여 있습니다. 파일 객체에 비하여 파일 기술자를 사용하면 더 좋은 점은 낮은-수준의 파일 연산 모둠에 접근할 수 있다는 것입니다. 그러면 파일과 그의 데이터를 더욱 더 섬세하게 제어할 수 있습니다. 파일 기술자를 파일 객체로부터 또는 그 반대로 만들 수 있습니다. 그렇지만 일반적으로 연산 모드를 하나의 함수나 프로그램 안에서 섞어 쓰는 것은 별로 좋지 않습니다. 파일 기술자를 단독으로 쓰거나 파일 객체를 쓰세요.

파일 기술자 함수에는 모든 파일 연산이 들어 있습니다. 그 동안 익혀 온 open이나 read 그리고 writeclose가 모두 포함되어 있습니다. 낮은-수준의 루틴은 보통 더 사용하기 어렵고 일이 잘못될 가능성이 높습니다. 그러므로 오직 절대로 그래야 할 경우에만 낮은 수준으로 접근하세요. 대부분의 경우 표준 파일 객체가 더 좋은 해결책입니다.

어떤 환경에서 낮은-수준의 파일 접근이 필요할까요? 표준 연산은 buffered IO라는 개념을 사용합니다. 데이터는 읽고 쓰는 동안에 버퍼로 저장 구역에 보유됩니다. 어떤 경우는 그런 버퍼 때문에 기이한 하드웨어에 접근하거나 초단시간 연산을 수행할 때 문제가 야기됩니다. 그런 경우 낮은-수준의 연산이 해답이 될 수 있습니다. 그러나 왜 사용해야 하는지 확신하지 못한다면, 아마도 그러지 않는 편이 좋을 것입니다!

앞 문단에서 거대한 약점을 보여주었으므로 이제 낮은-수준의 루틴을 이용하는 것이 실제로는 그렇게 어렵지 않다는 사실을 말씀 드리겠습니다. 단지 조심해야 할 함정이 몇 개 있을 뿐입니다.

먼저 간단한 작업을 수행해 보겠습니다. 텍스트 파일을 열어 데이터를 조금 쓰고 다시 그 파일을 닫아보겠습니다. 다음과 같이 보입니다:

>>> fname = 'F:/PROJECTS/PYTHON/Root/FL.txt'

>>> mode = os.O_CREAT | os.O_WRONLY # 만들고 쓴다.

>>> access = 0777 # 모두 읽고/쓰고/실행.

>>> data = '텍스트가 제대로 작동하는지 점검한다'

>>> fd = os.open(fname, mode, access) # 주의. os 버전은 내장이 아님!

>>> length = os.write(fd, data)

>>> if length != len(data): print '씌여진 데이터 양이 데이터의 길이와 일치하지 않는다!'

>>> os.close(fd)

비슷하게 그 파일에서 데이터를 다시 읽어올 수 있습니다.

>>> mode = os.O_RDONLY # 읽기 전용

>>> fd = os.open(fname,mode) # 이 번에는 접근할 필요 없음

>>> result = os.read(fd, length)

>>> print result

>>> os.close(fd)

파일 접근 유형을 설정하는 방식은 여기에서 좀 더 어렵다는데 주목하세요. 비트 or 연산자를 사용하여 os 모듈이 제공하는 플래그를 모두 조합해야 합니다. 두 번째는 기본 값 말고 접근 레벨을 제공할 수 있음에 주목하세요. 이는 표준 파일 객체 메쏘드에 비하여 더 좋은 점입니다. 8진수 (0으로 시작하기 때문에 8진수임) "파일의 허가권 변경하기"라는 제목으로 위에 기술한 것과 똑 같습니다. 데이터를 읽을 때 읽을 데이터의 길이를 건네 주어야 하며, 이는 표준 read 메쏘드로도 할 수 있지만 낮은-수준의 연산에서는 필수입니다.

읽고 쓰는 실제 데이터는 언제나 일련의 바이트입니다. ASCII 문자열을 다룰 때는 각 문자가 한 바이트씩 차지하기 때문에 문제가 아닙니다. 그러나 다른 데이터 유형에 대해서는 파일 다루기 주제에서 기술한 바와 같이 struct 모듈을 사용해야 합니다.

프로세스 조작하기

운영체제의 사용자로서 가장 흔하게 하는 일은 프로그램을 실행하는 것입니다. 보통 이런 일은 GUI 안이나 명령어 줄 쉘을 통하여 하지만 프로그램을 또다른 프로그램 안에서 실행시키는 것도 가능합니다. 어떤 경우는 프로그램을 기동시키고 실행을 완료하는 것이 다이지만, 또 어떤 경우는 입력과 데이터를 제공하고 그 출력을 다시 프로그램 안으로 읽어 들이기도 합니다.

이를 위한 기술적 용어는 IPC(프로세스간 통신(inter-process communication))이라고 부릅니다. 다음 주제에서 좀 자세하게 다루어 보겠습니다.

그래서 "프로세스" 무엇인가?

프로세스(Process)는 실행중인 프로그램을 일컫는 환상적인 컴퓨터 과학 용어입니다. 그리하여 컴퓨터에 실행 파일이 있고 그것을 실행하면, 프로그램을 자신만의 메모리 공간 안에서 실행됩니다. 같은 실행 파일을 여러번 실행시킬 수도 있습니다. 각 파일마다 따로따로 메모리공간을 가지고 자신의 데이터를 처리합니다. 이렇게 운영체제와 함께 실행중인 프로그램을 이른바 프로세스라고 부릅니다.

운영 체제가 제공하는 도구를 사용하면 컴퓨터에서 실행중인 프로세스를 볼 수 있습니다. Windows NT/2000이나 XP를 실행중이라면 Ctrl-Alt-Del를 누르면 작업 관리자가 기동되는데, 프로세스라는 탭을 보면 실행중인 프로세스들이 긴 목록으로 나타납니다. 그 이름중 어떤 것은 알겠고 또 어떤 것은 모를 겁니다. 왜냐하면 윈도우즈 자체가 기동시킨 프로세스이기 때문입니다. 또 어떤 어플리케이션은 여러 프로세스를 기동시킨다는 것을 눈치채실지도 모르겠습니다 - 관계형 데이터베이스와 웹 서버가 종종 이렇게 합니다. 본인의 PC에서 작업 관리자 프로세스의 모습은 다음과 같이 보입니다:

리눅스나 MacOS에서는 ps라는 Unix 명령어로 실행중인 프로세스의 목록을 볼 수 있습니다. 본인의 ps에서 결과는 다음과 같습니다:

무료 인터넷 백과사전인 위키피디아에서 프로그램프로세스 그리고 실행파일 등등 여러 용어에 대하여 유용하게 그리고 더 완벽하게 정의합니다.

외부 프로그램 실행하기 - os.system()

그래서 이제 프로그램과 프로세스 사이의 차이점을 이해하였으므로 파이썬에서 프로그램을 어떻게 실행할 수 있는지 살펴봅시다. 제일 쉬운 방법은 os 모듈에서 system() 함수를 사용하는 것입니다. 이는 그냥 명령어 문자열을 실행하고 그 명령어가 올바로 종료하였는지를 반영하는 에러 코드를 돌려줄 뿐입니다. 요청된 프로그램의 실제 출력결과에 접근할 방법이 없습니다. 또 실행중인 프로세스에 입력을 제공할 방법도 없습니다. 그러므로, system() "기동하고 잊어 버려도 되는" 프로그램에 적합합니다. 예를 들어, 터미널 화면을 지우기 위해 그 명령어가 성공적으로 완료되었는지 알 필요조차 없습니다. 일단 시작하면 그 명령어와 상호작용할 필요가 없습니다. 아래와 같이 유닉스 유형의 운영 체제에서 이런 예를 보실 수 있습니다 :

>>> import os

>>> errorcode = os.system("clear")

0

MS DOS/Windows 기반의 운영체제라면 그 명령어는 약간 다릅니다:

>>> errorcode = os.system("CLS")

0

그러나 그 결과는 두 경우 모두 터미널 창이 청소되고 에러 코드가 성공적으로 완료되었음을 나타내는 0이라는 것입니다. 이는 별로 유용해 보이지 않을지라도 명령어 고유의 출력만 화면에 보여준다거나 또는 명령어의 성공 또는 실패에만 관심이 있을 경우 스크립트에 system을 훌륭하게 사용할 수 있습니다. 예를 들어, 파일이름을 인자로 하여 ls 명령어를 요청하면 리눅스 컴퓨터에서 파일이 특정한 위치에 존재하는지 알아낼 수 있습니다.

>>> filename = 'xxyyzz.ggh'

>>> errorcode = os.system('ls %s > /dev/null' % filename)

>>> if errorcode != 0: print filename, 'does not exist'

예제는 여러 테크닉을 보여줍니다. 먼저 문자열 형식화를 이용하여 시스템 호출을 매개변수로 넘겨주는 법을 보여줍니다. 둘 째 출력을 /dev/null으로 방향전환시켜서 터미널에 인쇄되지 않도록 제압하는 법을 보여줍니다 (또는 OS /dev/null 개념을 전혀 지원하지 않은 경우 임시 파일에 방향전환할 수 있습니다.). 마지막으로, 에러코드를 통역하여 연산의 결과를 결정하는 법을 보여줍니다.

파일이 존재하면 에러 코드는 0이 됩니다. 그러나 존재하지 않으면 0-아닌 에러 값을 얻습니다. 물론 위에서 파이썬 고유의 함수를 사용하여 그 존재 여부를 점검하는 더 우아한 방법을 보았습니다. 그러나 그 원리는 다른 명령에도 사용될 수 있습니다.

system은 사용하기 아주 쉽지만 별로 유연하지 못하며 직접적으로 데이터를 프로그램에 다시 송신하는 방법이 없습니다. 그 출력을 임시 텍스트 파일에 잡아서 그 파일을 열어 예와 같이 처리하면 이를 속일 수 있습니다. 그러나 같은 결과를 달성하는 더 좋은 방법이 있습니다. popen이라는 또다른 os 모듈 함수를 이용하면 됩니다.

윈도우즈 사용에게 드리는 주의사항

다음 예제 코드는 모두 유닉스 ps 명령어를 사용하며 윈도우즈에서는 작동하지 않습니다. 특히 commands 모듈은 윈도우즈에서 사용할 수 없으므로 그 부분은 완전히 건너 뛰셔도 좋습니다. 다른 예제들은 윈도우즈에서 작동하도록 수정할 수 있습니다. 명령어 문자열을 윈도우즈 용으로 다음과 같이 "DIR /W"로 수정하면 됩니다. 프로세스를 실행하기 위한 윈도우즈 종속적인 테크닉을 설명하고 이 섹션을 마치겠습니다. 불행하게도 윈도우즈 운영 체제와의 상호작용이 뚜렷하게 이식성이 없는 곳이 바로 이곳입니다!

외부 프로그램 실행하기 - os.popen()

사실은 popen에 여러 변종이 있습니다. 이른바 popen, popen2, popen3 그리고 popen4가 있습니다. 뒤의 숫자는 다양하게 데이터 스트림을 조합할 수 있음을 가리킵니다. 표준 데이터 스트림은 사용자와 대화하기 주제에서 사이드바에 설명하였습니다. 기본 popen 버전은 그냥 하나의 데이터 스트림을 만들고 그 함수에 건네지는 모드 매개변수에 따라 거기에서 모든 입력/출력이 전송/수신됩니다. 본질적으로 popen은 명령어를 실행한 출력결과를 마치 파일 객체를 사용하는 것처럼 보이게 만듭니다.

대조적으로, popen2는 두 개의 스트림을 제공합니다. 하나는 표준 출력을 위해서이고 다른 하나는 표준 입력을 위해서입니다. 그래서 데이터를 프로세스에 보내고 프로세스를 닫지 않고서도 출력을 읽을 수 있습니다. popen3stdin/stdout외에 stderr 접근을 더 제공합니다. 마지막으로 popen4가 있는데 이는stderrstdout을 하나의 스트림에 조합해 넣어서 마치 보통의 콘솔 출력처럼 보입니다. 파이썬 2.4에서 이 모든 popen 호출은 새로운 Popen 클래스로 교체되었습니다. 이 클래스는 새로운 subprocess 모듈에서 볼 수 있고 나중에 살펴보겠습니다. 지금은 오직 표준 os.popen() 함수만 살펴보겠습니다. 다른 것들은 연구 과제로 남겨두겠습니다!

위의 콘솔 출력에 보이는 ps 명령의 출력 결과를 어떻게 읽을 수 있는지 생각해 봅시다. 앞서 언급했듯이, popen은 명령어를 마치 파일처럼 보이게 만듭니다. 그래서 그 명령어를 읽기 모드 문자열로 열면 파일류의 객체를 되돌려 줍니다. 그러면 readline이나 read 등등의 파일 연산을 적용할 수 있습니다. ps같이 상호작용이 없는 연산에 대해서 가장 쉬운 방법은 프로그램이 실행되도록 두고 read()를 사용하여 전체 출력을 하나의 문자열로 쓸어 모으는 것입니다. 그러면 string.split()를 사용하여 그 문자열을 줄별로 나눌 수 있습니다. 다음과 같이 말입니다:

import os

psout = os.popen('ps -ef', 'r')

results = psout.read().split('\n')

for line in results:

   print line

화면에 보여준 것과 같은 정보를 제공합니다. 그러나 이제 데이터에 접근할 수 있으며 파이썬의 문자열 조작 능력을 사용하여 필요한 대로 아무 행이나 열을 추출할 수 있습니다. 예를 들어 파이썬 인터프리터에 대한 프로세스 ID를 뽑아낼 수 있습니다. 적절한 행을 찾아서 3번째 필드를 읽으면 됩니다 (또는 5번째 마지막 필드가 더 유용합니다. 여기에서 사용자 ID는 잠재적으로 1개 또는 2개의 단어이기 때문입니다. 그래서 거꾸로 작업하면 정확할 가능성이 더 높습니다.).

그것을 시험해 보겠습니다:

for line in results:

   if 'python' in line:

       print 'The python pid is:', line.split()[-5]

       break

보시다시피 이것이 system을 사용하는 것보다 훨씬 더 강력합니다. 임시 파일 등등을 만들 필요가 없습니다.

또한 popen을 사용하여 프로세스에 쓸 수도 있지만, 이는 상대적으로 흔하지 않은 일입니다. 이 작업을 더 손쉽게 해 주는 다른 모듈이 있습니다. 예를 들어 telnet 모듈이 있습니다. 이 주제에서는 프로세스에 쓰는 법을 연구하지 않겠습니다. 그러나 그럴 필요가 생긴다면 popen이 프로세스를 파일처럼 보이게 만들어주며 그 파일은 읽기와 쓰기용으로 열릴 수 있다는 사실을 기억하세요.

최신 파이썬 버전에는 이런 종류의 프로세스 상호작용을 더욱 더 간소화시켜주는 모듈과 함수가 추가되었습니다. 이제 이런 편의 함수를 살펴보겠습니다.

외부 프로그램 접근을 위한 다른 메커니즘

파이썬은 commands라는 모듈을 제공하는데 이 모듈은 유닉스 기반의 시스템에서 popen을 약간 더 쉽게 사용할 수 있는 포장자를 제공합니다. ps로부터 프로세스 아이디를 뽑아내는 위의 예제를 아래에서 commands 모듈을 사용하여 다시 작성해 봅시다:

>>> import commands as c

>>> psout = c.getoutput('ps -ef').split('\n')

>>> for row in psout:

...   if 'python' in row:

...     print row.split()[-5]

...

3268

>>>

문자열을 행별로 가른(split) 다음에 그 행을 필드로 자를(split) 필요가 있다는 것을 주목하세요. 앞에서 popen으로 그랬던 것처럼 말입니다. 그러나 이 번에는 명시적으로 그 출력을 읽을(read) 필요가 없습니다. 대신에 commands.getoutput를 한 번만 호출해도 그 명령어를 실행하고 그 결과를 열람합니다. 모듈에 있는 다른 함수로 종료 코드에 접근할 수도 있습니다.

파이썬 2.4에서 도입된 또다른 파이썬 모듈은 subprocess 모듈입니다. 이 모듈의 의도는 명시적으로 위에서 연구한 메커니즘을 모두 대체하는 것입니다. 사용하는 법은 모듈 문서에 있지만 여기에서 기본적인 사용법을 살펴보겠습니다. 이 모듈은 Popen이라는 클래스 위에 기반합니다 - 첫 글자가 대문자임에 주목하세요!

Popen 클래스는 한 명령어의 실체를 만드는데 사용될 수 있습니다. 불행하게도 문서는 Popen 구성자에 매개변수가 너무 많기 때문에 약간 위압적입니다. 좋은 소식은 그 매개변수들은 거의 모두 기본 값이 있으며 단순한 사례에서는 무시해도 좋다는 것입니다. 그리하여 그냥 OS 명령어를 스크립트 안에서 실행하려면 다음과 같이 하면 됩니다:

import subprocess

p = Popen('ps -ef', shell=True)

shell=True 인자에 주목하세요. 이는 그 명령어를 운영 체제의 명령어 처리기 즉 쉘(shell)에게 이해시키려면 꼭 필요합니다.

call이라는 함수도 있습니다. 이 함수는 위의 예제에 사용된 os.system을 교체하는데 사용될 수 있습니다:

subprocess.call('ps -ef', shell=True)

이 수준에서 call은 위에 기술한 Popen의 사용법과 거의 동일합니다. 그러나 callPopen에 있는 옵션에서 일부만을 가지고 있으며 실체를 전혀 만들지 않습니다. 그래서 시스템 자원을 약간 덜 소비합니다.

os.popen()과 동등하지만 좀 복잡합니다.

import subprocess

psout = subprocess.Popen('ps -ef', shell=True, stdout=PIPE).stdout

results = psout.read().split('\n')

for line in results:

   print line

고지: 여기에서 주요 차이점은 'r' 모드 문자열을 제공하는 대신에 stdoutPIPE이어야 하고 Popen 실체의 stdout 속성을 psout에 할당하라고 지정했다는 것입니다. 나머지 코드는 앞의 예제와 하나도 바뀌지 않고 그대로입니다.

다른 os.popen 변종들도 거의 똑 같이 흉내낼 수 있습니다. 어느 표준 스트림이 파이프로 표현될 필요가 있는지 지정하면 됩니다. (파이프(pipe)는 그냥 다른 프로세스에 데이터 접속일 뿐입니다. 이런 경우 실행중인 프로세스와 명령어 사이에 말입니다) 다양한 스트림에 할당가능한 유효한 값들은 파일 기술자나 다른 스트림이 포함됩니다 (예를 들어 stderrstdout처럼 보이게 만들 수 있습니다). 다음 문서에서는 각 os.popen 함수들을 교체하는 법을 더 자세하게 보여줍니다.

예전 함수보다 subprocess를 사용하면 더 크게 개선된 것은 요청된 명령어가 발견되지 않으면 subprocess 모듈이 OSError 예외를 일으킨다는 것입니다. 예전 함수들은 일반적으로 에러가 있다는 표시를 확실하게 남기지 않았습니다!

나중에 프로세스- 통신 주제에서 subprocess의 사용법과 데이터 파이프의 개념을 더욱 깊이 살펴보겠습니다.

보안은 어떤가?

오늘날 컴퓨터 보안에 관하여 많이 언급됩니다. 안전한 환경을 보장하는 편의기능들은 대부분 운영 체제가 제공합니다. OS API를 통하여 다른 OS 특징을 이용할 수 있는 것처럼 보안의 특징에도 접근할 수 있습니다. 그러나 그러려면 중대한 전제조건이 있습니다. 운영체제는 여전히 그 프로그램을 실행하고 있는 사용자에게 허용된 권한에 의거하여 특정 특징으로의 접근을 규제하고 있어야 합니다. 그래서 다른 사용자의 파일에 접근 권한을 얻고 싶으면 어쨌거나 허락을 받을 필요가 있습니다. 시스템의 내장 보안을 회피하는 일은 장려할 것이 못 됩니다 - 적어도 그래서는 안됩니다!

이 섹션에서는 보안에 관련된 함수들을 살펴보겠습니다. 예를 들어 사용자 아이디를 결정하거나 파일의 소유권을 바꾸거나 또는 마지막으로 환경 변수를 이용하여 현재 사용자의 환경에 관하여 알아 보겠습니다.

사용자와 파일 소유권

첫 과업으로서 현재 사용자의 ID를 알아내려면 os.getuid 함수를 이용하면 됩니다. 사용자 ID는 숫자의 형태로 되어 있고 그 숫자를 사용자 이름으로 바꾸는 일은 약간 복잡하지만 거의 그럴 필요가 없습니다. 왜냐하면 보통 getpass.getuser() 함수를 사용하면 사용자의 이름을 얻을 수 있기 때문입니다. 이 함수는 그냥 그 정보를 보유하고 있을 다양한 환경 변수를 살펴볼 뿐입니다. 다음과 같이 사용합니다:

>>> import getpass

>>> print getpass.getuser()

그렇지만 사용자 ID는 프로그램이 보안 설정을 수정하는데 필요한 값입니다. 그래서 다음과 같이 얻습니다:

>>> import os

>>> print os.getuid()

아마도 이를 가장 많이 사용하는 예는 프로그램적으로 파일의 소유권을 바꾸는 것일 겁니다. 어쩌면 이전에 이 자습서에서 만든 파일을 바꾸고 싶을 수도 있겠군요. 예를 들어 이 주제에서 앞서 만든 파일 중의 하나를 사용하겠습니다:

import os

 

os.chdir(r'F:\PROJECTS\Python\Root')

os.system('ls -l *.txt')

id = os.getuid()

os.chown('FA.txt',id,-1)

os.system('ls -l *.txt')

system()을 사용하여 chown()를 호출하기 전과 후의 디렉토리 목록을 나열합니다. 그래서 변화를 볼 수 잇도록 말입니다. getuid()로부터 얻은 사용자 ID를 가지고 chown()을 호출했습니다. 그리고 -1chown()의 세 번째 매개변수에 지정하여 그룹의 소유권은 바꾸고 싶지 않다는 뜻을 나타냈습니다. (If we did 그룹 아이디를 가져오는데 사용되는 os.getgid() 함수도 있습니다). 현재 소유자와 다른 사용자로 스크립트를 실행했을 때만 효과가 있는 것을 주목하세요. 또한 그 사용자는 그 변화에 영향을 미치려면 권한이 있어야 합니다. 그래서 관리자( 'root')로 로그인 하시기를 권장합니다.

chown()은 결과에 관하여 아무 것도 알려주지 않습니다. 그래서 그 결과를 알고 싶으면 stat 같은 모듈을 사용하여 그 사용자 id의 값이 전과 후로 바뀌었는지 살펴보고 예상했던 대로 실제로 일어났는지 그 변화를 점검해야 합니다.

사용자 환경

이 섹션에서는 프로세스가 실행되는 환경(environment)을 살펴보겠습니다. 프로그램은 시작할 때 자신을 기동시킨 프로그램으로부터 모든 메모리 정황을 물려 받습니다. 이는 보통 사용자 명령어 줄 쉘입니다 - MS DOS 또는 유닉스 기반 시스템이라면 Bash 쉘이나 Korn 쉘일 수 있습니다. 그런 환경에는 수 많은 시스템 정보가 포함되어 있습니다. 사용자 이름이나 홈 디렉토리 그리고 현재 디렉토리와 임시 디렉토리, 검색 경로 등등의 정보가 있습니다. 이것은 다양한 환경 변수(environment variables)를 설정하면 사용자마다 어느 정도까지는 운영체제의 방식과 개별 프로그램까지도 재단할 수 있다는 뜻입니다. 예를 들어 파이썬은 모듈을 찾을 때 PYTHONPATH 환경 변수에 주의를 기울입니다. 그래서 두 명의 사용자가 같은 컴퓨터에 있다면 모듈 검색 경로가 다를 수 있습니다. 왜냐하면 각 사용자가 따로 PYTHONPATH를 설정하기 때문입니다.

프로그래머는 이 사실을 이용하여 프로그램 종속적 환경 변수를 정의하고 그러면 사용자는 그 환경변수를 설정하여 보통의 프로그램 기본 값을 오버라이드 할 수 있습니다. 이것이 효과가 있으려면 현재 환경을 읽어서 이런 값들을 찾을 수 있어야 합니다. 이렇게 하기 위하여 os.getenv() 함수를 사용하여 변수 하나를 읽거나 또는 이름/값 쌍의 사전이 들어 있는 os.environ 변수를 봄으로써 현재 설정된 변수를 모두 읽습니다.

무엇보다 환경 변수를 모두 인쇄할 텐데, 그러면 아마도 이 리스트에 정보가 너무나 많다는 사실에 놀라실지도 모르겠습니다:

>>> import os

>>> print os.environ

바로 이것입니다! 이 보다 더 쉬울 수는 없습니다. 물론 원한다면 표준 사전과 문자열 연산을 이용하여 약간 더 예쁘게 꾸밀 수는 있습니다. 그렇지만, 대부분의 경우 한 번에 하나 씩 변수의 값에 접근하는 것이 훨씬 더 유용합니다. 다음과 같이 접근할 수 있습니다:

>>> os.getenv('PYTHONPATH')

이렇게 하면 PYTHONPATH 변수가 설정되어 있는지 알 수 있고, 그렇다면, 무엇이 설정되어 있는지 알 수 있습니다.

getenv()의 전형적인 사용예는 프로그램을 초기화할 때인데 초기화시에 데이터 파일이 있는 폴더 같은 것들을 설정합니다. 다음 예제에서는 주소록이 어디에 저장되는지 알아 보겠습니다. 아무 변수도 없으면 현재 디렉토리의 기본값을 사용합니다:

# ... 다른 초기화는 이 곳에서 처리한다.

folder = os.getenv('PY_ADDRESSES', os.getcwd())

# ... 나머지 프로그램은 여기에 둔다.

PY_ADDRESSES 변수에 아무 값도 없으면 getenv()는 자신의 두 번째 인자를 돌려줍니다. 이 인자가 기본 위치입니다.

보통 사용자는 그런 환경 변수를 운영 체제에서 손수 만들고 설정합니다. 예를 들어 Windows XP라면 다음과 같이

내컴퓨터->속성->고급->환경 변수

순서대로 설정하면 됩니다.

리눅스나 MacOS라면 명령어 프롬프트에서 사용중인 쉘에 따라 exportsetenv 명령어를 사용하여 설정합니다.

어떤 운영 체제에서는 아무 것도 없어도 기존의 환경 변수의 값을 변경할 수 있습니다. 이렇게 한다면 아주 조심하세요. 어떤 시스템은 결과적으로 다른 값을 덮어 쓸 가능성이 있기 때문입니다. 또 어떤 운영체제는 이런 변화를 다시 사용자의 환경에 복사해 주지만 대부분의 경우 그 변화는 쓰기 프로세스의 문맥에만 적용됩니다.

그래서 OS가 지원한다면 기본 폴더 값을 다시 사용자 환경에 써서 프로그램의 복제본들이 같은 위치를 사용할 수 있도록 보장해 줄 수 있습니다.

# 위와 같은 코드

putenv('PY_ADDRESSES', folder)

# ... 나머지 프로그램에 따라다닌다.

어떤 유닉스 환경 변수는 많은 프로그램에 사용됩니다. 예를 들어:

·         EDITOR - 어느 편집 프로그램을 사용자가 사용하고 싶어하는지 결정합니다. 전형저으로 'ed', 'vi', 'vim' 또는 'emacs'입니다. 다른 프로그램에서 사용자가 텍스트 파일을 편집하려고 한다면 그 프로그램을 실행할 수 있습니다.

·         PRINTER - 사용자가 파일을 어떻게 인쇄하고 싶어하는지 결정합니다.

·         PAGER - 사용자가 선호하는 파일 보기 프로그램을 결정합니다. 이 값은 보통 'more', 'less' 또는 'view' 중 하나로 설정됩니다.

환경에 관한 언급은 이 정도로 마치겠습니다. 나중에 다른 주제에서 다시 다루겠습니다. 그러나 지금 당장 사용자-종속적 데이터에 어떻게 접근하는지 궁금하다면 이미 환경 변수로 있는지 알아 보시고 그렇지 않으면 대안으로 사용자에게 프로그램에 종속적인 환경 변수를 통하여 설정하도록 선택권을 주세요.

아직도 넘어, !

os 모듈과 그의 친구들에는 단 한 개의 주제에 다루기에는 너무 많은 것이 들어 있습니다. 사실 파이썬 문서조차도 os 모듈 하나만 설명하는데에도 여러 HTML 페이지가 들어갑니다. 다른 모듈이라면 한 페이지가 드는 것에 비해서 말입니다. 모쪼록 풍요롭게 제공되는 기능을 만끽해 보세요. 거기에서 기이하고 경이로운 이름들을 많이 발견할 것입니다. 이런 이름들은 대부분 유닉스 운영체제와 그의 API에서 온 것들입니다. os 모듈은 최선을 다해 어느 운영체제에나 동등한 기능을 제공합니다. 그러나 이런 함수들이 무슨 일을 하는지 좀 더 알고 싶다면, 종종 제일 좋은 방법은 유닉스 문서를 읽는 것입니다. 시작하기에 좋은 곳은, 특히 유닉스/리눅스 시스템을 갖추지 못했다면, 오라일리 책 Unix Systems Programming for SVR4입니다.

이렇게 운영체제 연구가 관심을 불러 일으킨다면, 운영체제 일반에 관한 훌륭한 책을 한 권 권합니다. 제임스 리스터(James Lister)가 집필한 Fundamentals of Operating Systems가 그것입니다. 읽기에 짧고 쉬우며 많은 개념들을 다이어그램으로 설명합니다. 코드에 더 가까이 접근하고 싶다면, 앤드류 타넨바움(Andrew Tanenbaum)의 고전보다 더 좋은 책은 없습니다: Operating Systems: Design And Implementation. 이 책은 리누스 토발즈(Linus Torvalds)가 자신의 운영 체제를 작성하는 동기가 되었습니다. 이것이 계속 발전해서 리눅스라는 명품이 되었습니다!

기억해야 할 것들

· OS는 프로세스가 실행될 수 있는 환경을 제공한다.

· OS는 컴퓨터 하드웨어에 접근을 제공한다.

· OS는 보통 C로 작성된 API를 통하여 접근할 수 있습니다.

· 파이썬의 os 모듈은 OS API 위에 포장자를 제공합니다.

· os.path 모듈과 glob 모듈로 파일에 편리하게 접근할 수 있습니다.

· system(), popen(), command() 그리고 Popen은 모두 다양한 수준에서 프로세스와 IPC를 제어합니다.

· getuid(),getenv() 그리고 관련 특징을 이용하면 사용자와 그의 설정정보에 관하여 알 수 있습니다.

 

'Python > 파이썬 프로그래밍 연습' 카테고리의 다른 글

CGI 서버 예제  (0) 2012.05.07
데이터베이스 작업  (0) 2012.04.25
실전에 사용되는 파이썬  (0) 2012.04.25
사례 연구  (0) 2012.04.25
기능적 프로그래밍  (0) 2012.04.25

댓글