본문 바로가기

별걸다하는 IT/리눅스 유닉스

[리눅스/유닉스] expect 사용법, sftp/ssh 다른 서버 원격 제어하기 컨트롤하기

반응형

[리눅스 유닉스 완전정복 포스팅 링크 모음]

안녕하세요 ㅎㅎ

오늘은 파일송수신이나 원격으로 제어하는 스크립트를 짤 때 개인적으로 유용하게 사용했던 expect 사용법을 알아보려고 합니다.

 

[목차]

1. expect란 무엇인가, 왜 사용하는가

2. expect 설치여부 확인하고 설치하기

3. expect 명령어 종류

4. expect 스크립트 예시

 

expect란 무엇인가, 왜 사용하는가

여러분 챗봇 아시죠??

내가 '오늘은 몇요일이야?' 라고 질문하면 그 대답에 맞춰서 챗봇이 '일요일입니다' 요런식으로 대답을 해주잖아요

이렇게 질문과 답변이 왔다갔다 하는 방식을 interactive라고 하는데요. 주거니 받거니~!

 

만약에 매일 아침마다 날짜나 요일을 알려주는 프로그램을 만든다고 해요.

기존 프로그램을 활용하면, 날짜 계산할 필요 없이 매일 9시마다 '오늘 몇요일이야?', '오늘 며칠이야?'만 질문을 날리게끔 만들면 되는거잖아요!

오늘 몇요일이야? 라고 질문했을 때 대답이 월요일, 화요일, 수요일, 목요일, 금요일, 토요일, 일요일이면 그대로 포워딩하고 그 외 이상한 답변이 나올 경우에는 '알 수 없음'으로 대체하는 방식으로 개발하는거죠~

 

그럼 여기서 중요한점은

내가 저쪽에다가 질문을 보내는 방법과

저쪽으로부터 특정 대답을 받았을 때 그걸 처리할 수 있도록 자동화 하는건데요.

man expect

expect라는게 기대하다라는 뜻이죠? 커맨드를 보냈을 때 어떤 반응이 올지 내가 기대한다, 잡아챈다 요런 뜻이예요.

커맨드를 주거니 받거니 할 수 있도록 즉, 대화를 자동화 할 수 있게 도와주는 명령어입니다.

 

오늘은 몇요일이야? 이거를 나 대신에 자동으로 셸이 쳐주고, 답변이 월요일이야 왔을 경우에는 어떤 특정 행동을 하도록 대화를 자동화하는거죠

 

Expect is a program that "talks" to other interactive programs according to a script.
expect는 스크립트에 따라 상호작용적인 프로그램과 대화하는 프로그램이다.

 

좀 더 상세한 예로 저는 이걸 배포시스템 만들 때 사용했는데요

개발서버에서 내가 배포셸을 돌리면, expect가 나 대신에 알아서 sftp 비밀번호까지 쳐서 로그인 한 후 테스트서버에 바이너리 파일을 복사하게끔 개발했답니다.

암튼 그렇게 쓰일 수 있다는거~ 좀 더 자세한 이해는 아래 글을 좀 더 읽어가면서 완성하도록 해요~

 

EXPECT 설치여부 확인하고 설치하기

[expect 설치여부 확인]

설치여부 확인은 매우 심플합니다 ㅋㅋ

1. expect

2. which expect 

무론 이외에 dpkg나 설치명령어로 확인할 수 있지만 이는 명령어가 1,2번보다 기므로 생략!

간편한게 최고죠~

커맨드창에 expect라고 쳐보세요.

ubuntu@server:~$ expect
expect: not found

요런식으로 not found 결과가 뜨면 설치 안되어있는거 

 

또는 which expect로 명령어 실행파일 경로를 확인해볼 수도 있습니다.

no expect in 이런식으로 뜨거나 아무것도 뜨지 않으면 설치가 안되어있는 거고 

/usr/bin/expect 요런 경로가 뜨면 설치되어있는 거예요.

 

[expect 설치하기]

레드햇계열 OS일 경우 (CentOS, 페도라)

sudo yum -y install expect;

 

데미안계열 OS일 경우 (ex 우분투)

sudo apt install expect -y;

만약에 관리자(root)계정이라면 sudo 빼도 됩니다.

빨간색 밑줄친 부분이 설치하는 명령어인데요

이후 쭉 잘 설치진행중인것을 확인할 수 있습니다.

설치한 후에 명령어를 쳐봤더니 커맨드가 잘 먹네요~~

반응형

 

EXPECT 명령어 사용법, 명령어 종류

간단한 expect 명령어 몇가지만 알고 넘어갑시다.

전체 명령어는 man expect로 보시면 확인할 수 있어요

 

[요약]

■ set timeout

디폴트 타임아웃 시간을 지정합니다. 지정하지 않을 경우 10초로 세팅됩니다.

 

■ spawn 

명령어를 그대로 실행해줍니다.

 

spawn telnet $svr_ip $svr_port

요렇게 하면 svr_ip변수에 저장되어있는 서버IP값(ex 168.10.1.2)과

svr_port변수에 저장되어있는 포트값(ex 8080)을 가져와서

telnet 168.10.1.2 8080 요렇게 만든 다음에 해당 명령어를 커맨드창에서 실행합니다.

고정 값이 아닌 변경가능성이 있는 데이터일 경우 spawn을 사용하면 내가 명령어를 직접 치는 것처럼 상황에 맞게 수행할 수 있어요.

 

■ expect 

프로세스로부터 특정 문자열을 기다립니다 (기대합니다)

 

■ send 관련

타겟 서버로 문자열을 보냅니다.

expect가 받는거면 send는 보내는거~

 

■ exp_continue

expect가 반복되어서 처리됩니다.

expect 반복해서 사용하기 싫을 때 사용합니다.

타임아웃 시간도 리셋됩니다.

 

■ interact

expect 종료 후 사용자에게 제어권을 넘깁니다.

 

■ exit

expect를 종료합니다.

 

[상세 설명]

이제부터는 키워드를 기준으로 하나씩 자세히 설명해드릴게요

 

■ 스크립트 작성시

#!/usr/bin/expect

expect 스크립트를 만들 때에는 expect 프로그램을 사용하겠다는 셔뱅을 맨 위에 선언해줘야 합니다.

which expect해서 실행파일 경로 나오는 곳을 기재해주면 돼요.

 

[인자로 받을 때]

함수에 인자 넘겨주듯이, expect 스크립트 또한 코드기 때문에 인자를 넘겨줄 수 있는데요

lindex $argv [인자순번]

요렇게 쓰면 됩니다

set user [lindex $argv 0]
set server_ip [lindex $argv 1]
set password [lindex $argv 2]

첫 번째 인자 값을 user 변수에 저장

두 번째 인자 값을 server_ip 변수에 저장

세 번째 인자 값을 password 변수에 시작 

 

[스크립트 확장자]

참고로 expect 스크립트는 .exp 확장자로 저장해주면 됩니다.

expect test.exp

요 명령어는 test.exp를 실행해라~ 라는 의미

 

■ timeout 관련

set timeout 5 ##타임아웃을 5초로 설정한다.

타임아웃 지정하는 명령어예요. 해당 명령어를 사용하지 않으면 디폴트 타임아웃이 10초로 지정됩니다.

 

timeout { exit 2 }

set timeout말고 그냥 timeout도 쓰이는데요.

타임아웃이 될 경우 리턴값2로 종료시킨다. 라는 뜻이예요

 

expect -timeout 5 "password:"

expect와 -timeout도 같이 사용할 수 있습니다.

"password:"라는 문자열이 뜨기를 5초간 기다린다는 의미예요.

 

■ spawn 관련

명령어를 그대로 실행해줍니다.

만약에 제가 스크립트에 spawn ping naver.com 

요렇게 썼다면, 실제로 "ping naver.com"을 치는거랑 동일해요.

 

spawn으로 프로세스가 시작되면, spawn_id라는게 해당 프로세스를 가리키는 디스크립터로 세팅됩니다. 

그래서 같은 프로그램을 spawn으로 두 번 실행시켰을 때, 

둘 중 내가 선택한 프로그램에 명령어를 입력하도록 제어할 수 있어요.

 

## 플레이어1이 game 실행
spawn game 
## spawn_id 값을 game_id1 변수에 저장
set game_id1 $spawn_id 

## 플레이어2가 game 실행
spawn game
## spawn_id 값을 game_id2 변수에 저장
set game_id2 $spawn_id 

## 플레이어1 게임 동작 제어
set spawn_id $game_id1
(attack~)

대충 적어보면 이런 방식입니다. 

 

■ expect 관련

프로세스로부터 특정 문자열을 기다립니다 (기대합니다)

 

expect안쓰고 그냥 sftp를 쳤을 때 터미널에서 어떤 반응이 일어나는지 볼까요?

ubuntu@168.110.7.174's password: 라고 뜨면서

비밀번호를 입력하라고 나와요,

 

즉 내가 sftp명령어를 치고 제대로 이게 수행됐을 때에 기대하는 문자열은

"ubuntu@168.110.7.174's password: "가 되겠죠?

 

##sftp 계정@IP 명령어를 실행한다. 
spawn sftp ${SVR_USERNAME}@${SVR_IP} 
##명령어 실행시 "ubuntu@168.110.7.174's password:"를 답변으로 캐치했다면
expect "${SVR_USERNAME}@${SVR_IP}'s password:"
##비밀번호를 입력한다.
send "${PASSWORD}\r"

 

[-nocase 옵션]

근데 이게 운영체제마다,, 기대대는 값이 다를 수 있어요.

password:만 나올수도 있고 또는 Password:가 나올수도 있죠

expect에도 대소문자를 구별하지 않게 하는 옵션이 있는데 바로 -nocase입니다.

expect -nocase "password:"

대소문자 상관하지 않고 password: 문자열을 수신받기를 기다린다.

 

[-re 옵션]

그런데 대소문자는 구별하대, Password, password 두 가지만 허용케이스에 넣고 싶다던가

특정 패턴에 맞는 응답을 기다릴 때도 잇겠죠.

 

그럴 때를 대비해서 expect는 패턴만 맞으면 될 수 있도록 정규표현식을 사용할 수 있습니다.

그 옵션이 -re예요 (regular expression의 줄임말인듯)

expect -re {[Pp]assword}

요렇게 하면 Password나 password가 들어가 있으면 뽑아주겠죠~~!

 

[-timeout 옵션]

앞에서 했었지만,, expect와 타임아웃 조화입니당

expect -timeout 3 "hello"

"hello"가 뜨기를 3초간 기다림 

 

이 외에도 

expect eof : expect 스크립트를 명시적으로 종료

expect -d : 디버그 모드로 expect 실행하기

등등.. 다양한 구문들이 있어요.

 

 

■ send 

문자열을 보냅니다.

 expect "password:" { send "${SVR_PASSWORD}\r"}

"password:"라는 응답을 받으면, SVR_PASSWORD 변수 값에 저장된 데이터를 보낸다.

 

비번치라고 떳을 경우 비번을 스크립트가 자동으로 치고 로그인할 수 있게끔 만들어주는 코드죠.

\r은 엔터를 의미합니다.

 

[send_user]

send말고 send_user라는 것도 있는데요

send_user는 원격지 서버로 보내는게 아니라 로컬 stdout으로 문자열을 보내는 것을 말합니다.

 

■ exp_continue

expect가 반복되어서 처리됩니다.

타임아웃 타이머가 리셋됩니다.

 

expect {
    "hello" { send "hello\r"; exp_continue; }
    "thank you" { send "you're welcome\r"; exp_continue; } 
    "your name" { send "hamzzi\r" }
}

요렇게 쓰는거임~!

 

■ interact

expect 종료 후 사용자에게 제어권을 넘깁니다.

 

■ exit

expect를 종료합니다.

 

expect 작성 예시

앞에 설명드린 명령어를 알고 있으면 이제 왠만한 expect 코드는 이해할 수 있을텐데요~

가장 많이 사용하는 기본 중에 기본이거든요~~!

 

[bash 스크립트에서 expect 사용]

function connect_sftp_server {
    expect << EOF ##STDIN이 이걸로 소진돼 버려 interact가 작동하지 않는다.
    spawn sftp ${SVR_USERNAME}@${SVR_IP}
    expect "password:" { send "${SVR_PASSWORD}\r"}
    expect "sftp>" { send "cd ${DIR}\r"}
    expect {
    	##디렉터리 존재하지 않아 못찾을 경우
    	"Couldn't state remote file" {  
        	exit 2
        }
        "sftp>" {
        	send "put ${FILE}\r"
        }
    }
    expect "sftp>" { send "quit\r" }
EOF
}

예시로 sftp로 서버에 접속하는 함수를 작성한 코드를 들고 왔습니다.

요런식으로 작성하면 돼요.

 

[expect 스크립트]

#!/usr/bin/expect
set USER [lindex $argv 0]
set IP [lindex $argv 1]
set PW [lindex $argv 2]

set timeout 5
spawn ssh -o StrictHostKeyChecking=no $USER@$IP
expect {
    "(yes/no)?" { send "yes\r"; exp_continue }
    -re "password" { send "${PW}\r" }
    exit 
}

간단하게 오늘은 expect 가 무엇인지

또 어떻게 사용하는지 기본적인 문법을 다뤄봤습니다.

도움이 되셨다면 공감은 큰 힘이 됩니다. 다음에 봐요~~

반응형