tmux 사용에 도움되는 설정과 플러그인 정리

😢 이 페이지는 다음 주소로 변경될 예정입니다.

최근에 구입한 Dell 노트북에 조금이라도 가볍게 사용해보려고 Lubuntu를 설치해서 사용하고 있다. 트랙패드가 예전에 비해 많이 나아지긴 했지만 아무래도 맥북에서 사용하던 것과는 많이 달라서 좀 더 키보드 친화적인 환경을 꾸려야겠다는 생각이 들었다. 그러던 중 tmux와 다시 친해질 기회인 것 같아서 tmux를 설치하게 되었다.

어제 tmux 이야기를 트위터에 올렸더니 ujuc님이 powerline이란 멋진 tmux 플러그인을 소개해주시고, 사용하는 rc 파일을 공유해주셨다.

만약 tmux 플러그인에 관심이 있다면 이 페이지가 도움이 된다.

그 외에도 간단하게 설치할 수 있는 것도 많이 보였다.

구글 검색해보면 이것저것 유용한 도구가 많이 나온다.

tmux 설정 되돌리기

tmux.conf 재미있는 점이 default 파일이 존재하지 않는다는 점이다. 설정 하나를 변경하면 기존 설정을 알지 못하는 이상 다시 기본 설정으로 돌아갈 수가 없다. 그 눈 아픈 기본값 초록색 상태 막대로 한번에 돌아갈 방법이 없다는 뜻이다.

그래서 tmux 기본 설정을 어딘가 추출해서 보관해두면 다시 돌아오는데 편리하다. 현재 tmux에 설정된 값은 다음 명령어로 추출할 수 있다.

$ tmux show -g | sed 's/^/set-option -g /' > ~/.tmux.current.conf

구글링 해보면 멋지게 꾸며진 tmux.conf를 많이 볼 수 있다. 나처럼 설정을 잘 모르고 적용했다가 명령을 시작하기 위해 사용하는 프리픽스인 Ctrl + b를 이상한걸로 변경해서 종료도 못하고 오고가도 못하는 상황을 마주할 수도 있으니 꼭 기본 설정을 추출해두자.

tmux.conf를 적용하는 명령은 source-file이다.

$ tmux source-file ~/.tmux.current.conf

직접 설정 변경하기

내 경우는 터미널 폰트를 비트맵으로 사용하고 있어 앞서 powerline을 적용하니 대다수가 깨져 이쁘게 적용되질 않았다. 게다가 사양 탓인지 좀 느려지는 기분이라서 간단하게 색상 바꾸고 필요한 것만 설치하기로 했다.

tmux에서 가장 필요했던 부분은 배터리 잔량 표시와 일자/시간 표시였다. 일자/시간은 기본적으로 가능한 부분이라 배터리 잔량 표시는 다음 프로그램을 설치했다.

아쉽게도 잔량 표시 그림은 그려지지 않지만 수치가 나오니 그럭저럭 만족하고 있다.

기분 전환 겸 상태 막대 색상도 초록에서 연한 회색(colour235)로 변경했다. 사용할 수 있는 색상은 다음 스크립트로 확인할 수 있다.

for i in {0..255} ; do
    printf "\x1b[38;5;${i}mcolour${i}\n"
done

누가 이 결과를 보기 좋게 github에 올려뒀다.

원하는 색상이 나오지 않을 때

이 색상 설정은 256color 모드로 실행하지 않은 터미널에서는 동작하지 않는다. 색상이 적용되지 않는다면 다음 설정을 참고하자.

# .bashrc or .zshrc 에 추가
export TERM=xterm-256color
alias tmux="tmux -2"

# .tmux.conf 에 추가
set -g default-terminal "screen-256color"

OS X의 터미널은 기본적으로 256color로 설정되어 있다.


tmux의 기본적인 기능은 예전 요약했던 내용이나 nanhapark님의 포스트를 참고하면 되겠다. 물론 이런 요약본도 tmux.conf 한방에 모두 변경될 수 있어서 tmux.conf를 조심하자는 이상한 결론을 내려본다.

당분간 개인적으로 사용하는 환경이 터미널 위주라서 이참에 tmux와 vim에 부지런히 친해지는 계기로 삼으려고 한다. 손목 시큰함도 좀 줄었으면 좋겠다.

Dell Inspiron 11 3000 구입기

😢 이 페이지는 다음 주소로 변경될 예정입니다.

그간 사용하던 맥북 에어를 보내고 집에 있던 넷북으로 간간히 작업을 하고 있었는데 어느 날 갑자기 넷북 디스플레이가 나가버렸다. SSD까지 교체해서 그나마 빨라졌나 했더니 더이상 사용할 수 없게 되어버렸다. 새 맥북이 조만간 나온다길래 기다리려 했는데 해야 할 일이 많아 더이상 미룰 수 없어 맥북 나오기 전까지 사용할 용도로 저렴한 노트북을 하나 구입하게 되었다.

터미널 구동할 정도만 되면 충분하다는 생각에 11인치 정도 선에서 가장 저렴한 노트북을 알아봤다. 작은 사이즈를 고르려니 선택할 수 있는 폭이 좁았다. 후보로 Lenovo ideapad 100s(한국서는 i-slim어쩌고), HP Stream 11, Dell inspiron 11 3000 을 골랐다. Lenovo 제품은 Officeworks에서 행사중이라서 250불 가량이고 HP와 Dell은 300불 정도였다. 스펙은 셋 다 비슷해서 가서 만져보고 구입하기로 결정했다.

키보드는 Lenovo가 마음에 들었지만 Dell 키보드도 괜찮았고 크기도 적당해서 Dell로 구입했다. 사진으로는 HP Stream가 가장 괜찮아 보였는데 실제로 보니 키보드도 그렇고 생각보다 별로였다. Dell inspiron 11 3000을 JB HiFi에서 워런티 포함 AUD305 주고 구입했다. 친구랑 빨간색 사야한다 얘기하면서 갔는데 흰색만 있어서 흰색을 구입했다.


Dell Inspirion 11 3000

Dell inspiron 11 3000 작년 모델은 2-in-1 인데 2016 모델은 그냥 랩탑으로 나왔다. 모델도 두 종류인데 하나는 셀러론 모델이고 하나는 펜티엄 모델이다. 구입한 셀러론 모델의 스펙은 다음과 같다.

  • Intel® Celeron® Processor N3050 (2M Cache, up to 2.16 GHz)
  • Windows 10 Home with OneDrive 64-bit English
  • 2GB Single Channel DDR3L 1600MHz
  • 32GB eMMC Storage
  • Intel® HD Graphics
  • 11.6-inch HD (1366 x 768) Anti-Glare LED-Backlit Display
  • Wireless 802.11ac + Bluetooth 4.0, Dual Band 2.4&5 GHz, 1×1
  • 32 WHr, 2-Cell Battery (Integrated)
  • Integrated Widescreen HD (720p) Webcam with Digital Microphone
  • 45 Watt AC Adapter
  • Height: 18.45-19.88mm (0.73-0.78″) x Width: 292mm (11.5″) x Depth: 196mm (7.72″)
  • Starting at weight 1.18Kg (2.6lbs)
  • up to 9 hours 34 mins
  • HDMI v1.4a, USB 3.0 x 1, USB 2.0 x 1
  • Noble lock security slot, Micro SD card reader (SD/SDHC/SDXC), Headphone/Mic

이 가격대는 브랜드를 불문하고 같은 스펙이라서 사실 큰 의미는 없었다.

Dell Inspirion 11 3000 keyboard

외관은 구 맥북의 미니어처 버전 정도 되는 인상을 받았다. 뚜껑도 반들반들해서 먼지 잘 붙을 것 같지만 흰색이라 티가 잘 안난다. 키보드는 검정이라 괜찮지만 팜래스트는 흰색이라서 때가 좀 탈 것 같다. 디스플레이도 저가형을 감안하고 화면이 나온다는 데에 의의를 둔다면 그럭저럭 이해 할 만 하지만 시야각이 좀 많이 좁은 편이다. 액정도 살짝 어두워서 밝은 곳에서는 조금 침침하게 느껴진다. 스피커 음량은 적당한 편이다.

하드웨어에서 가장 아쉬운 부분은 전원이 켜졌거나 충전중임을 표시하는 인디케이터가 전혀 없다는 점이다. 그리고 메모리타입이라 그런지 fan도 들어있지 않아 소리가 전혀 나지 않는다. 이게 장점인지 단점인지 미묘한데 가볍게 사용하면 크게 발열은 나지 않는데 발열이 조금 나기 시작하면 잘 식지 않는 것 같다.

Dell Inspirion 11 3000

기본적으로 Windows 10이 설치되어 있는데 Dell 제품이 다 그런지 몰라도 McAfee 한 달 이용권이랑 Dropbox 20GB 1년 이용권이 있었다. 켜자마자 맥아피 제품이 구동되며 컴퓨터가 멈춰서 버벅임을 뚫고 바로 McAfee를 삭제했다. 환경 구성하는데 시간을 딱히 사용하고 싶지 않아서 그냥 windows로 사용하려고 했었는데 OEM이라 그런지 설치되어 있는 기본 프로그램이 너무 많아 상당히 무거워서 lubuntu를 설치했다.


장점은 저렴한 가격과 휴대성이고 단점은 성능 정도 될 것 같다. 구 맥북 디자인을 좋아한다면 디자인도 장점이 될 수 있을 것 같다. 할 일이 많은데 도움이 되었으면 좋겠다.

JavaScript의 Generator와 Koa.js 소개

😢 이 페이지는 다음 주소로 변경될 예정입니다.

사이드 프로젝트에서 Express를 오랜 기간 사용했었는데 hapi 가 좋다는 얘기를 듣고는 hapi를 많이 사용해왔다. Hapi도 단순하긴 하지만 “설정만 넣으면 되는” 단순함이라서 설정에 들어가는 수고가 꽤 컸다. 최근에는 토이 프로젝트에서 API를 작성하는데 에러 발생 여부에 따라서 {"ok": true} 하나 넣어주는 작업에 오만가지 코드를 작성해야 했다. express와 다르게 미들웨어에서 request, response에 접근할 수 있는 포인트가 워낙에 많아 더 복잡하게 느껴졌다. 그러던 중 예전에 잠시 비교글로 봤던 koa를 살펴봤는데 지금 필요한 상황에 맞는 것 같아 koa로 다시 코드를 작성했고 마음에 드는 구석이 많아서 간단한 소개를 작성한다.

Koa는 ES2015의 문법 중 하나인 제너레이터를 적극적으로 활용하고 있는 웹 프레임워크다. 모든 요청과 처리를 제너레이터를 활용해 파이프라인을 만드는 것이 특징이며 그 덕분에 깔끔한 async 코드를 손쉽게 작성할 수 있다. Express 만큼은 아니더라도 다양한 라이브러리를 제공하고 있고, express의 라이브러리나 미들웨어도 thenify나 co로 변환해서 활용할 수 있을 만큼 확장성이 높다.

이 포스트는 제너레이터를 먼저 살펴보고, 제너레이터를 유용하게 사용할 수 있는 co를 살펴본 후, KoaJS를 간단하게 살펴보는 것으로 마무리한다.


제너레이터 Generator

다른 언어에도 이미 존재하고 있기 때문에 크게 특별한 기능은 아니지만 ES6에서의 구현을 간단히 정리하려고 한다.

일반적인 함수의 경우, 매 실행마다 같은 흐름으로 모든 코드를 실행하지만 Generator 함수는 실행 중간에서 값을 반환할 수 있고, 다른 작업을 처리한 후에 다시 그 위치에서 코드를 시작할 수 있다. 이 제너레이터는 반복 함수 iterator를 next()로 제공하고 결과를 value로, 진행 상황을 done으로 확인할 수 있다.

구구단을 제너레이터로 작성하면 다음과 같다.

function* nTimesTable(n) {
  for(var i = 1; i <= 9; i++) yield n * i;
}

제너레이터는 위와 같이 function* fnName(){} 식으로 *을 넣어 선언한다. 익명 함수의 경우도 function*(){} 식으로 선언한다.

이제 이터레이터(iterator)를 nineTimesTable에 반환 받는다.

var nineTimesTable = nTimesTable(9);

이터레이터는 next()를 통해 실행할 수 있다. 이 함수로 중단한 위치의 결과가 반환된다.

var result = nineTimesTable.next();
console.log(result); // { value: 9, done: false }
result = nineTimesTable.next();
console.log(result); // { value: 18, done: false }
result = nineTimesTable.next();
console.log(result); // { value: 27, done: false }

// keep calling...

result = nineTimesTable.next();
console.log(result); // { value: 72, done: false }
result = nineTimesTable.next();
console.log(result); // { value: 81, done: false }
result = nineTimesTable.next();
console.log(result); // { value: undefined, done: true }

매 반복 실행에서 value를 반환하지만 동시에 done으로 해당 함수가 yield 결과 없이 종료되었는지 확인할 수 있다. 마지막에 별도의 return 값이 없기 때문에 valueundefined가 된다.

이런 이터레이터의 반환 특징을 이용하면 다음과 같이 iterator를 호출하는 함수를 작성할 수 있다.

function caller(iter) {
  var result, value;
  while(result = iter.next()) {
    if(result.done) break;
    value = result.value || value;
  }
  return value;
}

var result = caller(nTimesTable(3));
console.log(result); // 27

donetrue를 반환할 때까지 해당 이터레이터를 실행해 결과값을 가져오는 caller를 작성했다. 만약 매 반복에서 특정 함수를 실행하고 싶다면 다음처럼 작성할 수 있다. 앞서 작성한 nTimesTable 함수가 더 많은 내용을 반환하도록 수정했다.

function * nTimesTable(n) {
  for(var i = 1; i <= 9; i++) yield { n: n, i: i, result: n * i };
}

function caller(iter, func) {
  var result, value;
  while(result = iter.next()) {
    if(result.done) break;
    value = result.value || value;
    if(func) func(value);
  }
  return value;
}

caller(nTimesTable(3), value => {
  console.log('%d x %d = %d', value.n, value.i, value.result);
});

앞서 작성한 caller는 제너레이터 내의 yield에 대해서는 처리를 하지 못한다. 제너레이터에서 이터레이터를 반환하고 진행을 중단했을 때 해당 이터레이터를 처리해서 다시 반환해야 한다. 결과를 넣고 다시 진행할 수 있도록 작성해야 하는 것이다.

function* getAnimalInCage() {
  yield "Wombat";
  yield "Koala";
  return "Kangaroo";
}

function* Cage() {
  var cageAnimals = getAnimalInCage();

  var first = yield cageAnimals;
  var second = yield cageAnimals;
  var third = yield cageAnimals;

  console.log(first, second, third);
}

Cage 제너레이터를 실행하면 yield를 3번 사용했기 때문에 최종 console.log가 출력하는 결과를 보기까지 4번에 걸쳐 실행된다.

var cage = Cage();
var firstStop = cage.next();
// {value: iterator, done: false}

첫 번째 yield 결과가 firstStop에 저장되었다. cageAnimals는 위에서 코드에서와 같이 getAnimalInCage 제너레이터가 생성한 이터레이터다. 이 이터레이터에 next() 메소드로 값을 받은 후, 그 값을 다시 first 변수에 다음과 같이 반환한다.

var firstAnimal = firstStop.value.next();
// firstAnimal: {value: "Wombat", done: false}
var secondStop = cage.next(firstAnimal.value);

next의 인자값으로 첫 결과인 Wombat을 넣었다. 이전에 멈췄던 위치인 첫 번째 yield로 돌아가 함수 내 first에는 Wombat이 저장된다. 나머지도 동일하게 진행된다.

var secondAnimal = secondStop.value.next();
// secondAnimal: { value: 'Koala', done: false }

var thirdStop = cage.next(secondAnimal.value);
var thirdAnimal = thirdStop.value.next();
// thirdAnimal: { value: 'Kangaroo', done: true }

var lastStop = cage.next(thirdAnimal.value);

// Wombat Koala Kangaroo

마지막 Kangaroo는 yield가 아닌 return이기 때문에 done이 true를 반환한다. 앞서 직접 호출해서 확인한 코드는 반환하는 값이나 호출하는 형태가 일정한 것을 볼 수 있다. 즉 재사용 가능한 형태로 만들 수 있다는 의미다.

다음은 catchEscapedAnimal()getTodaysZookeeper() 함수를 이용한 Zoo 제너레이터 예시다.

function catchEscapedAnimal() {
  return function(done) {
    setTimeout(function() {
      done(null, {name: 'Kuma', type: 'Bear'});
    }, 1000);
  };
}

function* getTodaysZookeeper() {
  yield {status: 'loading'};
  return {status: 'loaded', name: 'Edward'};
}

function* Zoo() {
  var animal = yield catchEscapedAnimal();
  var zookeeper = yield getTodaysZookeeper();

  console.log('%s catches by %s', animal.name, zookeeper.name);
}

catchEscapedAnimal()은 ajax를 사용하는 경우를 가정해서 setTimeout을 이용해 콜백을 호출하는 형태로 작성되었다. getTodaysZookeeper()는 일반적인 제너레이터 함수로 첫 호출에는 loading을, 두번째 호출에서 최종 값을 전송한다. Zoo도 앞에서 본 Cage처럼, 중간에 yield를 사용한다. 이 함수를 처리하기 위한 compose 함수는 다음과 같다.

function compose(iter, value, next) {
  var result = iter.next(value);
  if(result.done) return next ? next(value) : value;
  else if(typeof result.value == 'function') {
    return result.value(function(err, data) {
      if(err) throw err;
      compose(iter, data);
    });
  } else if(typeof result.value.next == 'function') {
    var _iter = iter;
    next = function(result){
      compose(_iter, result);
    };
    iter = result.value;
    result = iter.next();
  }
  return compose(iter, result.value, next);
}

compose 함수는 다음과 같은 경우의 수를 다룬다.

  • yield 된 값이 함수일 때, 호출 체인을 연결할 수 있도록 next 함수를 넘겨줌 (기존 callback 방식)
  • yield 된 값이 이터레이터일 때, 이터레이터가 done을 반환할 때까지 호출한 후 최종 값을 반환
  • 그 외의 결과를 반환할 때, 해당 값을 이터레이터에 넣고 다시 compose를 호출
  • 이터레이터가 종료(done == true)되었을 때, next 함수가 있다면 해당 함수로 호출을 진행하고 없으면 최종 값을 반환하고 종료

이 함수를 이용한 결과는 다음과 같다. setTimeout()에 의해 중간 지연이 진행되는 부분도 확인할 수 있다.

compose(Zoo());
// Kuma catches by Edward

제너레이터를 코루틴으로, co

나름 잘 동작하지만 흐름을 보기 위해서 만든 함수라서 허술한 부분이 많다. 이런 부분에서 사용할 수 있는 것이 바로 co다. co는 제너레이터를 코루틴처럼 사용할 수 있도록 돕는 라이브러리로 앞서 작성했던 compose 함수와 같은 역할을 한다.

var co = require('co');
co(Zoo());
// Kuma catches by Edward

이 라이브러리는 내부적으로 Promise 패턴을 사용하고 있어서 callback이든 Promise든 제너레이터든 모두 잘 처리한다. 실제로 제너레이터를 사용하고 싶다면 이 라이브러리를 사용하는 것이 큰 도움이 된다.

Koa

Koa는 앞서 이야기한 co 라이브러리를 기본적으로 적용하고 있는 HTTP 미들웨어 라이브러리로 경량에 간단한 기능을 제공하는 것을 특징으로 한다. 제너레이터를 기본적으로 사용할 수 있어서 앞서 배운 내용을 손쉽게 적용할 수 있다.

코드를 작성하기에 앞서 간단하게 koa를 설치한다.

$ npm install --save koa

Hello World를 작성하면 다음과 같다.

var koa = require('koa');
var app = koa();

app.use(function* () {
  this.body = {"message": "Hello World"};
});

app.listen(3000);

이제 http://localhost:3000에 접속하면 해당 json이 출력되는 것을 확인할 수 있다.

앞서 작성한 코드도 포함해보자.

var koa = require('koa');
var app = koa();

function catchEscapedAnimal() {
  return function(done) {
    setTimeout(function() {
      done(null, {name: 'Kuma', type: 'Bear'});
    }, 50);
  };
}

function* getTodaysZookeeper() {
  yield {status: 'loading'};
  return {status: 'loaded', name: 'Edward'};
}

function* Zoo() {
  var animal = yield catchEscapedAnimal();
  var zookeeper = yield getTodaysZookeeper();

  this.body = { message: animal.name + ' catches by ' + zookeeper.name };
}

app.use(Zoo);
app.listen(3000);

Koa의 모든 추가 기능은 미들웨어 구조로 제너레이터를 통해 작성하게 된다. callback은 물론 Promise 패턴도 더 깔끔하게 사용할 수 있다.

요청과 응답은 모두 this에 주입되서 전달되고 흐름은 첫 인자에 next를 추가해 제어할 수 있다. 요청에 대한 응답 내용이 있으면 ok를 추가해보자.

app.use(function* (next) {
  yield next;
  if(this.body) {
    this.body.ok = true;
  } else {
    this.body = { ok : false };
  }
});

다음과 같은 방식으로 토큰 검증도 가능하다.

app.use(function* (next) {
  var requestToken = this.request.get("Authorization");
  var accessToken = yield AccessTokensModel.findAccessTokenAsync(token);
  if(accessToken) {
    yield next;
  } else {
    this.body = { error: 'invalid_token' };
  }
});

세부적인 내용은 koa 웹페이지에서 다루고 있다. 단순하고 간편한 기능을 원한다면 꼭 살펴보자. 실제 사용하게 될 때는 koa-bodyparser, koa-router와 같은 패키지를 같이 사용하게 된다. 패키지 목록은 koa 위키에서 확인할 수 있다.

제너레이터도 충분히 편한 기능이지만 koa는 현재 await/async 문법을 지원하기 위한 다음 버전 개발이 진행되고 있다. 더 가독성도 높고 다른 언어에서 이미 구현되어 널리 사용되고 있는 문법이라 더 기대된다.


더 읽을 거리