John_Kang - 시스템 엔지니어
펄을 접할 때 단연코 가장 흥미로운 것은 "게으름은 미학이다!"라는 철학입니다. 게으름(laziness) 그 자체를 어떻게 바라보는지는 크게 관심 없습니다만, 게을러지기 위한 노력들은 삶을 윤택(?)하게 만들어 주기 때문이 아닐까합니다. 이번에 다룰 주제를 통해서 다시 한번 게을러 보고자 합니다 :)
인기있는 유용한 유틸리티들은 항상 다양한 기능을 제공하기 위해
명령줄 옵션을 제공할 뿐만 아니라 때때로 부명령(subcommand)까지도 지원합니다.
하지만 이런 다양한 명령과 옵션을 오랫동안 기억하는 것은 쉬운 일이 아닙니다.
심지어 입력하다보면 오타가 발생하기도 하죠.
--help
옵션을 이용해 도움말을 확인하면 될 일이지만, 번거롭긴 합니다.
자! 명령줄 자동 완성 기능을 이용해 좀 더 게을러져 보죠!!! :-)
필요한 모듈은 다음과 같습니다.
직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.
$ sudo cpan Complete::Bash Complete::Util
사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.
$ cpan Complete::Bash Complete::Util
더불어 perlbrew가 필요하므로 다음 명령을 이용해 perlbrew를 설치합니다.
$ wget -O - http://install.perlbrew.pl | bash
우선 쉘이 명령줄을 어떻게 파싱하는지 알 필요가 있습니다.
$PATH
에 있거나 절대 경로인 명령어임두 번째 규칙은 너무나도 친숙하기 때문에 규칙이라는 점을 인지하지 못하는 경우가 대부분입니다.
명령어의 자동 완성 기능은 항상 첫 번째 인자에서만 활성화됩니다.
두 번째 인자부터는 명령어를 자동 완성 시키지 못합니다.
물론 time
과 같이 명령어를 인자로 취하는 경우는
쉘의 명령줄 파싱 규칙에 의해서 파싱되는것이 아니라
complete
에 미리 정의된 함수를 이용해 실행 가능한 명령어를 찾는 것입니다.
Bash는 다양한 방법으로 자동 완성을 도와주는 complete
내장 함수를 지원합니다.
somecmd
명령어에 대해서 Wordlist
를 작성해 두고 사용하려면 방법은 다음과 같습니다.
$ complete -W "one two three four" somecmd $ somecmd t<TAB><TAB> two three
함수(-F
)를 사용할 수도 있는데, 이 때 해당 함수는 두 개의 입력 인자를 전달받습니다.
COMP_WORDS
: 인자 리스트COMP_CWORD
: 배열 인덱스(현재 커서의 위치)그리고 반드시 COMPREPLY
에 사용 가능한 자동 완성 요소 목록을 담아야 합니다.
$ _foo() { local cur COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} COMPREPLY=($( compgen -W '--help --verbose --version' -- $cur ) ) } $ complete -F _foo foo $ foo <TAB><TAB> --help --verbose --version
다른 방법으로 펄 등의 외부 명령을 실행해서 자동 완성 목록을 만들 수도 있습니다.
지금부터 perlbrew를 자동 완성을 지원할 대상으로 정하고 하나씩 살펴보겠습니다.
가장 먼저 쉘에서 <TAB><TAB>
키가 눌렸을 때 전달된 인자가 무엇인지를 확인해야 합니다.
Complete::Bash 모듈이 제공하는 parse_cmdline()
함수를 통해 쉘의 인자들을 파싱할 수 있습니다.
실제로는 인자를 그대로 파싱하는 것이 아니라 Bash 쉘로부터 전달된
COMP_LINE
, COMP_POINT
환경 변수를 파싱하는 것입니다.
parse_cmdline()
함수는 다음과 같은 구조로 파싱한 결과를 반환합니다.
[ [], $cur_idx ]
$cur_idx
는 <TAB><TAB>
을 사용한 시점의 커서 인덱스 값입니다.
#!/usr/bin/env perl use strict; use warning; use Complete::Bash qw( parse_cmdline format_completion ); use Complete::Util qw( complete_array_elem ); my ( $words, $cur_idx ) = @{ parse_cmdline() }; my $given = $words->[$cur_idx]; ## from bash : perlbrew <TAB><TAB> ## $words = [ 'perlbrew', '' ]; ## $cur_idx = 1; ## <TAB><TAB>을 타이핑한 시점에 명령줄에서 정의된 인자 리스트는 총 2개 입니다. ## perlbrew 명령어와 작성중인 2번째 인자 입니다. ## 현재 커서는 2번째 배열인덱스에 있으므로 커서의 값은 배열 인덱스인 1입니다.
perlbrew
명령어는 주동작을 결정짓는 부명령(subcommand)을
필수 인자로 취하고 부가적인 옵션들을 취합니다.
$ perlbrew Usage: perlbrew command syntax: perlbrew <command> [options] [arguments] Commands: init Initialize perlbrew environment. info Show useful information about the perlbrew installation install Install perl uninstall Uninstall the given installation available List perls available to install lib Manage local::lib directories. alias Give perl installations a new name upgrade-perl Upgrade the current perl list List perl installations use Use the specified perl in current shell off Turn off perlbrew in current shell switch Permanently use the specified perl as default switch-off Permanently turn off perlbrew (revert to system perl) exec exec programs with specified perl enviroments. self-install Install perlbrew itself under PERLBREW_ROOT/bin self-upgrade Upgrade perlbrew itself. install-patchperl Install patchperl install-cpanm Install cpanm, a friendly companion. install-multiple Install multiple versions and flavors of perl download Download the specified perl distribution tarball. mirror Pick a preferred mirror site clean Purge tarballs and build directories version Display version help Read more detailed instructions Generic command options: -q --quiet Be quiet on informative output message. -v --verbose Tell me more about it. See `perlbrew help` for the full documentation of perlbrew, or See `perlbrew help <command>` for detail description of the command.
먼저 command
에 해당하는 부명령에 대해 자동 완성을 작성합니다.
Complete::Util 모듈이 제공하는
complete_array_elem()
함수는 미리 작성된 자동 완성 목록과
검색에 사용할 문자열을 취해 사용가능한 자동 완성 목록을 반환합니다.
그리고 우리는 Complete::Bash 모듈이 제공하는
format_completion()
함수를 이용해 출력합니다.
if ( $cur_idx == 1 ) { my $complete = complete_array_elem( # # 자동 완성 목록 # array => [qw( init info install uninstall available lib alias upgrade-perl list use off switch switch-off exec self-install self-upgrade install-patchperl install-cpanm install-multiple download mirror clean version help )], # # 검색할 문자열 # word => $given, ); print format_completion $complete; }
perlbrew
명령의 필수 인자인 부명령의 자동 완성 목록을 완성했습니다.
이제 해당 부명령에 필요한 옵션 및 인자를 처리해야겠죠.
옵션이 필요없는 명령어의 경우 더 이상의 자동 완성 기능이 필요없습니다. 다음은 옵션이 필요없는 부명령의 목록입니다.
init
list
off
switch-off
install-cpanm
install-patchperl
self-upgrade
version
upgrade-perl
list-modules
추가로 모든 명령에 대응하는 일반 옵션 항목은 다음과 같습니다.
-q
--quiet
-v
--verbose
명령줄에서 전달한 목록중 마지막 두 번째 요소($words[-2]
)를 이용하면
perlbrew
를 실행할 때 사용한 부명령을 확인할 수 있습니다.
if ( $words->[-2] eq 'init' || $words->[-2] eq 'list' || $words->[-2] eq 'off ' || $words->[-2] eq 'switch-off' || $words->[-2] eq 'install-cpanm' || $words->[-2] eq 'install-patchperl' || $words->[-2] eq 'self-upgrade' || $words->[-2] eq 'version' || $words->[-2] eq 'upgrade-perl' || $words->[-2] eq 'list-modules' ) { generic_options($given); } sub generic_options { my $given = shift; completion( [qw( --quite --verbose )], $given ); } sub completion { my ( $available_opts, $given ) = @_; my $res = complete_array_elem( array => $available_opts, word => $given, ); print format_completion $res; exit 0; }
perlbrew
의 info
부명령은 모듈을 인자로 받은 후 해당 모듈의 정보를 화면에 출력합니다.
COMMAND: INFO info [module] Usage: perlbrew info [ <module> ] Display useful information about the perlbrew installation. If a module is given the version and location of the module is displayed.
설치한 모듈의 목록을 후보로 보여줄 수 있으면 편리하겠죠?
perlbrew list-modules
명령으로 획득한 목록을 사용해 자동 완성 시켜보죠.
if ( $words->[-2] eq 'info' ) { completion( list_modules(), $given ); } sub list_modules { my @modules = qx( perlbrew list-modules ); return [ map { chomp; $_ } @modules ]; }
switch
와 uninstall
, use
부명령은 현재 perlbrew
로 설치한 펄의 버전 정보를 인자로 취합니다.
이번에는 perlbrew list
명령으로 획득한 펄 버전 정보 목록을 사용해 자동 완성 시켜보죠.
if ( $words->[-2] eq 'uninstall' || $words->[-2] eq 'use' || $words->[-2] eq 'switch' ) { completion( installed_perl(), $given ); } sub installed_perl { my @installed_perl = qx( perlbrew list ); return [ map { /(perl-\d\.\d\d\.\d)/ } @installed_perl ]; }
코드 구문을 하나 하나 설명하다보니 전체 코드가 눈에 들어오지 않겠군요. 작성한 전체 코드는 다음과 같습니다.
#!/usr/bin/env perl use strict; use warnings; use Complete::Bash qw( parse_cmdline format_completion ); use Complete::Util qw( complete_array_elem ); my ( $words, $cur_idx ) = @{ parse_cmdline() }; my $given = $words->[$cur_idx]; if ( $cur_idx == 1 ) { my $complete = complete_array_elem( # # 자동 완성 목록 # array => [qw( init info install uninstall available lib alias upgrade-perl list use off switch switch-off exec self-install self-upgrade install-patchperl install-cpanm install-multiple download mirror clean version help )], # # 검색할 문자열 # word => $given, ); print format_completion $complete; } if ( $words->[-2] eq 'init' || $words->[-2] eq 'list' || $words->[-2] eq 'off ' || $words->[-2] eq 'switch-off' || $words->[-2] eq 'install-cpanm' || $words->[-2] eq 'install-patchperl' || $words->[-2] eq 'self-upgrade' || $words->[-2] eq 'version' || $words->[-2] eq 'upgrade-perl' || $words->[-2] eq 'list-modules' ) { generic_options($given); } if ( $words->[-2] eq 'info' ) { completion( list_modules(), $given ); } if ( $words->[-2] eq 'install' ) { my $install_opt = [qw/ -f --force -j -n --notest --switch --as --noman --thread --multi --64int --64all --ld --debug --clang -D -U -A --sitecustomize /]; completion( $install_opt, $given ); } if ( $words->[-2] eq 'available' ) { completion( [qw / --all /], $given ); } if ( $words->[-2] eq 'uninstall' || $words->[-2] eq 'use' || $words->[-2] eq 'switch' ) { completion( installed_perl(), $given ); } sub completion { my ( $available_opts, $given ) = @_; my $res = complete_array_elem( array => $available_opts, word => $given, ); print format_completion $res; exit 0; } sub generic_options { my $given = shift; completion( [qw( --quite --verbose )], $given ); } sub list_modules { my @modules = qx( perlbrew list-modules ); return [ map { chomp; $_ } @modules ]; } sub installed_perl { my @installed_perl = qx(perlbrew list); return [ map { /(perl-\d\.\d\d\.\d)/ } @installed_perl ]; }
작성된 Bash 자동 완성 스크립트를 사용려면 몇 가지 작업을 더 필요합니다.
우선 실행 권한을 추가하고 complete
Bash 명령을 이용해 스크립트를 등록합니다.
$ chmod +x completion-perlbrew.pl $ complete -C completion-perlbrew.pl perlbrew
더불어 마지막으로 자동 완성 스크립트가 실행 가능해야 하기 때문에
이를 위해 PATH
변수를 이용해 위치를 지정해야 합니다.
또는 처음부터 스크립트를 등록할 때 절대 경로를 이용해도 됩니다.
# # PATH 변수 수정 # $ export PATH=$PATH:/home/user/bin # 또는 # # 절대 경로 등록 # $ complete -C /home/user/bin/completion-perlbrew.pl perlbrew
자동 완성을 적용하는 일련의 과정은 .bashrc
등의 파일을 이용하면 편리하겠죠. :)
지금까지 Bash 내장 명령어인 complete
와 펄이 어떻게 조화를 이룰 수 있는지 살펴보았습니다.
기사에서는 이해를 돕기 위해 간단한 부분을 소개했지만,
더 자세한 내용이 궁금하다면 CPAN의 Complete::Bash 모듈과
CPAN의 Complete::Util 모듈 문서를 참고하세요.
perlbrew는 많은 부명령과 옵션을 지원하는데,
소개하지 않은 나머지 부분에 대한 자동 완성은 여러분의 몫으로 남깁니다. :)
여러분이 만들 사용자 프로그램을 배포할 때도 자동 완성 스크립트를 같이 배포한다면 프로그램의 편의성을 한껏 높일 수 있을 것입니다. 래리가 말한 게으름이란 참 반어적 표현입니다. 게으름은 필연적으로 부지런함을 수반하죠. 명령어를 입력할 때마다 일일이 키보드를 치는 부지런함을 부릴지, 아니면 탭키를 누를 때마다 후보가 뜨게 하기 위해 키보드를 치는 부지런함을 부릴지는 순전히 여러분의 선택 사항입니다. :-D
Enjoy Your Perl & Merry Christmas! ;-)
EOT
X-mas tree
& Llama
ASCII Art by ASCII Art Farts.
Computer ASCII Art by Chris.com.
Font ASCII Art by TAAG.
Artwork by
Inkyung Park
& Keedi Kim.
Designed by
Hojung Youn
& Keedi Kim.
Articles by
Seoul Perl Mongers.
Edited by
Luzluna Park
& Keedi Kim.
Hosting generously sponsored by
Yuni Kim.
Sponsored by
SILEX.
.-''' __ __ / \/ \/ \ =-_- | \. -____- / \ // /|| '' //| //|| == = == ==