Python subprocess, pexpect

노력을 즐겼던 사람·2020년 8월 14일
1

flask api 서버와 os와 상호작용하기

아르바이트 중 이런 업무를 할당 받았다.

  1. root 권한에서 특정 user 권한으로 진입하기 (su - {username})
  2. 특정 user 권한에서 conda 환경 activate 하기 (conda activate {envs})
  3. conda 환경에서 특정 command 실행시키기
  4. 성공 여부에 따라 flask에서 프론트로 값 리턴하기

업무를 수행하기 위해서는 반드시 os에서 권한, 환경, 디렉토리를 넘나들며 파이썬 스크립트, 리눅스 커맨드를 실행해야 했다. 이런 업무를 수행할 수 있게 해주는 2가지 모듈이 있는데 subprocess와 pexpect였다. 이 두가지에 대해서 살펴보자

subprocess란?

새로운 프로세스를 만들어준다. 이 프로세스에 I/O/Error에 대한 파이프를 연결할 수 있다. subprocess는 python의 os.system, os.spawn* 모듈들을 대체한다.

subprocess.run()

일회성 프로세스를 생성할 때는 run() 메소드를 사용하자 python 3.5 이상부터 사용이 가능하고 조금 더 강력한 기능을 원한다면 Popen()을 사용하자

  • run()을 실행하는 코드
import subprocess

subprocess.run(['dir'], shell=True)

window 기준 해당 디렉토리에 존재하는 파일 리스트를 리턴한다. linux에서는 첫번째 args에 리스트 형태를 넘겨줄 때는 shell=False 로 지정하는데 window에서는 항상 True로 지정해야한다... 이상하네 문서를 읽다보니 dir, copy 같은 녀석들을 커맨드를 사용할 때는 shell=True로 설정하고 배치나 exe를 실행할 때는 shell=True가 필요 없다고 한다. 사용할 때는 어쨌든 리스트로 넘기는 이유는 shell injection 공격을 방지하기 위해서이다. 당연히 리스트 없이 그냥 커맨드를 넘겨도 된다.

['dir'] = 'dir' 이라는 뜻이다.

linux의 경우엔['ls'] 로 넘길 때는 shell=Fasle,'ls'로 넘길 때는 shell=True로 지정해야한다. 어쨌든 run() 메소드는 CompletedProcess 인스턴스를 리턴한다. 이녀석은 args, returncode, stdout, stderr, check_returncode() 로 이루어져있다.

여러 동작을 할 수도 있다. subprocess.run(['dir', 'findstr', 'workspace'], shell=True)

list의 원소는 command 기준 스페이스바의 공백과 파이프가 기준인 듯 하다.

['dir', 'findstr', 'workspace'] = dir | findstr workspace

subprocess.Popen()

Popen()은 run()보다 강력하다고 한다. 공식 문서의 매개변수들만 봐도 조금 더 많은 매개변수를 받을 수 있다. 기본적으로 사용하는 방식은 비슷하지만 communicate() 를 통해 Popen()을 통해 생성한 프로세스의 동작이 끝날 때 까지 기다렸다가 stderr, stdout을 받아 올 수 있다.
또한 stdin을 사용하여 input을 받고 파일을 입력할 수도 있다. stdin을 사용할 때는 꼭 flush()를 사용해야한다.

pexpect란?

pexpect는 ssh, ftp, mencoder, passwd 같은 녀석들과 상호작용하기 용이하게 디자인 되었다. 예시 코드를 살펴보자

import pexpect
child = pexpect.spawn('ftp ftp.openbsd.org')
child.expect('Name .*: ')
child.sendline('anonymous')
child.expect('Password:')
child.sendline('noah@example.com')
child.expect('ftp> ')
child.sendline('lcd /tmp')
child.expect('ftp> ')
child.sendline('cd pub/OpenBSD')
child.expect('ftp> ')
child.sendline('get README')
child.expect('ftp> ')
child.sendline('bye')

위의 코드는 두 개의 메인 메소드로 이루어진다.

  • expect(): 자식 어플리케이션이 string을 리턴하는 것을 기다린다. 문자열을 정규식으로 표현하여 복잡한 패턴들도 매치시킬 수 있다. expect()는 before 과 after 속성을 가진 자식 어플리케이션을 리턴한다. before은 기대되는(expected) 문자열 패턴을 포함하고 after는 기대된 패턴(expected pattern)에 의하여 매치가 된 문자열들로 이루어져있다.
  • send(): 자식 어플리케이션의 terminal에 매개변수에 입력된 string을 전송한다.

Popen()과 같이 프로세스를 생성하는 메소드는 spawn()이다.

대부분 ssh 접속을 자동화할 때 사용하는 듯 하다..

참고문서

불곰님 블로그 설명을 너무 잘해주셨다.

profile
노력하는 자는 즐기는 자를 이길 수 없다 를 알면서도 게으름에 지는 중

0개의 댓글