PEAR: Console_CommandLine

설치

Console_CommandLine 패키지는 명령행 인자를 파싱하는 PEAR 라이브러리입니다. 컴포져를 이용해서 설치하려면 composer.json에 다음 내용을 알맞은 위치에 추가하면 됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.php.net"
        }
    ],
    "require": {
        "pear-pear.php.net/Console_CommandLine": "*"
    }
}

기본 사용법

Console_CommandLine을 사용하기 위해서는 Console_CommandLine 인스턴스를 생성한 후에, addOption() 또는 addArgument() 메소드로 옵션이나 인자에 대한 명세를 설정한 후 parse() 메소드를 통해 파싱을 하면 됩니다. 이 과정 중에 명세에 맞지 않는 인자를 발견하면 예외가 발생합니다. 이 때 발생한 예외를 잡아서 적절한 사용법 안내를 화면에 출력합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

require 'vendor/autoload.php';

$parser = new Console_CommandLine(['name' =>'hello']);
$parser->description = 'Hello Program';
$parser->version = '1.0.0';

try {
    $result = $parser->parse();
    echo 'Hello.'.PHP_EOL;
} catch (Exception $e) {
    $parser->displayError($e->getMessage());
}

선 컴포져를 통해서 Console_CommandLine 패키지를 읽어들이기 위해 vendor/autoload.php를 불러들입니다. 그 다음 Console_CommandLine 인스턴스를 생성하면서 프로그램 이름을 “hello"로 설정합니다. 프로그램 설명은 “Hello Program"으로, 버전은 “1.0.0"으로 설정합니다. 설정은 인스턴스를 생성하면서 매개변수로 전달하여 할 수도 있고, 생성 후 프로퍼티에 값을 대입하여 할 수도 있습니다.

1
2
3
4
5
6
7
8
9
$ php example01.php -h
Hello Program

Usage:
  hello [options]

Options:
  -h, --help     show this help message and exit
  -v, --version  show the program version and exit

기본적으로 도움말(-h, –help) 옵션을 제공합니다. 버전(-v, –version) 옵션은 버전을 설정한 경우에만 사용 가능합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

require 'vendor/autoload.php';

$parser = new Console_CommandLine(['name' =>'hello']);
$parser->description = 'Hello Program';
$parser->version = '1.0.0';
$parser->addOption(
    'korean', 
    [
        'short_name' => '-k',
        'long_name' => '--korean',
        'description' => '한국어',
        'action' => 'StoreTrue'
    ]
);
try {
    $result = $parser->parse();

    if (isset($result->options['korean']) && $result->options['korean'] == true) {
        $hello = '안녕하세요.';
    } else {
        $hello = 'Hello.';
    }

    echo $hello.PHP_EOL;
} catch (Exception $e) {
    $parser->displayError($e->getMessage());
}

이번에는 인사를 한국어로 출력하는 옵션(-k, –korean)을 추가했습니다. addOption() 메소드를 이용해 옵션 이름은 “korean"으로 하고, 짧은 옵션은 -k, 긴 옵션은 --korean으로 설정했습니다. description은 옵션에 대한 설명으로 도움말(-h) 옵션 사용시 출력됩니다. action은 옵션을 지정했을 때 어떻게 처리하는가에 대한 값으로, 여기서는 -k 옵션을 지정하면 true 값으로 설정하도록 했습니다. (StoreTrue)

옵션값은 options 배열에 옵션 이름을 첨자로 사용해서 확인 할 수 있습니다. 옵션을 사용하지 않은 경우 null 값이 됩니다. 여기서는 StoreTrue 액션으로 지정했으므로, 한국어 옵션을 사용한 경우 true 값이 됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ php example-2.php -h
Hello Program

Usage:
  hello [options]

Options:
  -k, --korean   한국어
  -h, --help     show this help message and exit
  -v, --version  show the program version and exit

도움말을 출력 해보면 추가한 한국어 옵션을 확인 할 수 있습니다.

1
2
3
4
5
6
$ php example-2.php 
Hello.
$ php example-2.php -k
안녕하세요.
$ php example-2.php --korean
안녕하세요.

이제 한국어 옵션(-k, --korean)을 사용하면 한국어로 인사말을 출력합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

require 'vendor/autoload.php';

$parser = new Console_CommandLine(['name' =>'hello']);
$parser->description = 'Hello Program';
$parser->version = '1.0.0';
$parser->addOption(
    'korean', 
    [
        'short_name' => '-k',
        'long_name' => '--korean',
        'description' => '한국어',
        'help_name' => 'KOREAN',
        'action' => 'StoreTrue'
    ]
);
$parser->addArgument(
    'name',
    [
        'multiple' => true, 
        'description' => '이름',
        'help_name' => 'NAME'
    ]
);
try {
    $result = $parser->parse();
    if (isset($result->options['korean']) && $result->options['korean'] == true) {
        $hello = '안녕하세요.';
    } else {
        $hello = 'Hello.';
    }

    foreach ($result->args['name'] as $name) {
        echo "{$hello} {$name}.".PHP_EOL;
    }
} catch (Exception $e) {
    $parser->displayError($e->getMessage());
}

이름을 명령행 인자로 받아 인사를 하도록 해보았습니다. addArgument() 메소드를 통해 명령행 인자를 추가 할 수 있습니다. 명령행 인자 이름은 “name"으로, description은 “이름"으로, help_name은 “NAME"으로 설정했습니다. descriptionhelp_name은 도움말 화면을 통해 출력됩니다.

multipletrue로 설정하면, 인자를 여럿 받을 수 있습니다. 도움말에는 help_name 뒤에 숫자를 붙이고 말줄임표를 뒤에 덧붙여서 인자를 여럿 사용 할 수 있다는 것을 보여줍니다.

명령행 인자는 args 배열을 통해서 읽어들일 수 있습니다. 옵션과 마찬가지로 명령행 인자 이름 “name"이 첨자로 사용됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ php example-3.php -h
Hello Program

Usage:
  hello [options] NAME1 NAME2 ...

Options:
  -k, --korean   한국어
  -h, --help     show this help message and exit
  -v, --version  show the program version and exit

Arguments:
  NAME  이름

옵션 다음에 “NAME"인자를 쓸 수 있다고 도움말에 표시됩니다. 인자가 어떤 값인지에 대한 설명은 하단 “Arguments"부분에 출력됩니다.

1
2
3
4
5
$ php example-3.php bookworm
Hello. bookworm.
$ php example-3.php -k bookworm hellfire
안녕하세요. bookworm.
안녕하세요. hellfire.

XML

명령행 인자들에 대한 설정을 PHP 코드가 아닌 XML 파일을 이용해 할 수도 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<command>
    <name>hello</name>
    <description>Hello Program</description>
    <version>1.0.0</version>
    <option name="korean">
        <short_name>-k</short_name>
        <long_name>--korean</long_name>
        <description>한국어</description>
        <help_name>KOREAN</help_name>
        <action>StoreTrue</action>
    </option>
    <argument name="name">
        <description>이름</description>
        <multiple>true</multiple>
        <help_name>NAME</help_name>
    </argument>
</command>

fromXmlFile 메소드는 지정한 XML 파일을 읽어들이고 Console_CommandLine 인스턴스를 반환합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

require 'vendor/autoload.php';

$parser = Console_CommandLine::fromXmlFile('hello.xml');
try {
    $result = $parser->parse();

    // ...생략...
} catch (Exception $e) {
    $parser->displayError($e->getMessage());
}

다음 글에서는 옵션(Option)에 대해 더 자세히 다뤄보려고 합니다.

옵션

Console_CommandLine 패키지는 유용한 옵션들을 제공합니다. 이런 옵션들을 적절히 사용하면 깔끔하면서도 강력한 명령행 인자를 사용자에게 제공 할 수 있습니다.

Choices

1
2
3
4
5
6
7
8
9
$parser->addOption(
    'color', 
    [
        'short_name' => '-c',
        'action' => 'StoreString',
        'choices' => ['black', 'white'],
        'add_list_option' => true,
    ]
);

Choices는 정해진 값 중 하나만을 인자로 받도록 하는 옵션입니다. 예제처럼 색상을 흰색(white)과 검정색(black)만 받고 싶다면, choices에 받고자 하는 인자들을 배열로 정의하면 됩니다.

add_list_option은 기본값이 false지만, true로 설정하면 앞에 --list가 붙은 --list-color 옵션이 자동으로 추가됩니다. 이 옵션을 통해 사용자는 어떤 값들이 선택지로 제공되는지 확인 할 수 있습니다.

Counter

1
2
3
4
5
6
7
$parser->addOption(
    'verbose',
    [
        'short_name' => '-v',
        'action' => 'Counter',
    ]
);

Counter 옵션은 사용자가 여러번 중복으로 옵션을 사용했을 경우 사용한 횟수를 값으로 저장합니다. 예를 들어 -v -v -v와 같이 3번 사용했을 경우 verbose의 값은 3이 됩니다.

Password

1
2
3
4
5
6
7
$parser->addOption(
    'password',
    [
        'short_name' => '-p',
        'action' => 'Password',
    ]
);

Password 옵션은 사용자가 값을 생략했을 경우 프롬프트를 띄우고 직접 입력을 받도록 요청합니다. 이 때 사용자 입력은 화면에 출력되지 않습니다.

Callback

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function getByteSize($value, $option, $result, $parser, $params = [])
{
    static $sizeTable = ['K' => 1024, 'M' => 1048576, 'G' => 1073741824];

    // 오류 처리는 생략했습니다.
    preg_match('/^(\d+)([KMG])$/i', $value, $matches);

    return $matches[1] * $sizeTable[strtoupper($matches[2])];
}

$parser = new Console_CommandLine();
$parser->addOption(
    'size',
    [
        'short_name' => '-s',
        'action' => 'Callback',
        'callback' => 'getByteSize',
    ]
);

Callback 옵션은 인자 값을 콜백 함수를 통해 전처리하도록 합니다. 예제는 ‘10G’, ‘8M’ 같이 단위를 붙인 크기값을 바이트 단위로 변환하는 코드입니다.

True, False

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$parser->addOption(
    'debug',
    [
        'short_name' => '-d',
        'action' => 'StoreTrue',
    ]
);
$parser->addOption(
    'quiet',
    [
        'short_name' => '-q',
        'default' => true,
        'action' => 'StoreFalse',
    ]
);

True, False 옵션은 명령행 인자로 해당 옵션이 주어졌을 때 true 또는 false 값을 저장하도록 합니다. default를 생략한 경우, 옵션을 사용하지 않으면 null 값이 저장됩니다.

인자, 서브커맨드

프로그램이 인자(Argument)를 받아들이기 위해서는 addArgument() 메소드를 이용해 인자에 대한 정보를 매개변수로 전달해야 합니다.

1
2
3
4
5
6
7
8
$parser->addArgument(
    'INPUT',
    [
        'multiple' => true, 
        'optional' => true,
    ]
);
$parser->addArgument('OUTPUT', []);

위 코드는 INPUTOUTPUT 인자를 받을 수 있도록 한 것입니다. 여기서 multiple은 해당 인자가 여러 개 올 수 있다는 것이고, optional은 생략이 가능하다는 의미입니다. 바꿔말해 INPUT 인자는 생략하거나 여러개를 쓸 수 있고, OUTPUT은 반드시 하나만 입력해야 한다는 것입니다.

1
2
3
4
5
6
7
8
9
 Usage:
  example-10.php [options] [INPUT1 INPUT2 ...] OUTPUT

Options:
  -h, --help  show this help message and exit

Arguments:
  INPUT   
  OUTPUT  

도움말을 통해 이를 다시 확인 해보면 생략 가능하다는 의미로 대괄호로 INPUT을 감싸고 있고, 여러개 사용이 가능하다는 의미로 뒤에 숫자를 붙인 후 말줄임표를 뒤에 덧붙입니다.

서브커맨드(Subcommand)는 git처럼 프로그램 실행시 사용 할 수 있는 보조적인 명령어입니다. 예를 들면 git clone ... 같은 명령어에서 clone 부분이 서브커맨드입니다. 서브커맨드가 옵션이나 인자와 다른 점은 서브커맨드 자체적인 옵션과 인자를 가질 수 있다는 것입니다. 바꿔말해 -a 옵션을 그냥 사용했을 때와 서브커맨드 뒤에 붙여서 사용 했을 때 동작이 서로 다르게 할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$copy = $parser->addCommand('list');
$copy->addOption(
    'all',
    [
        'short_name' => '-a',
        'action' => 'StoreTrue',
    ]
);

$remove = $parser->addCommand('remove');
$remove->addOption(
    'recursive',
    [
        'short_name'  => '-r',
        'action'      => 'StoreTrue'
    ]
);

$result = $parser->parse();
var_dump($result->command_name);
var_dump($result->command->options);
var_dump($result->command->args);

위 코드는 listremove 두개의 서브커맨드를 설정하고 있습니다. list-a 옵션을, remove-r 옵션을 가지고 있습니다.

1
2
$ php ./example-11.php list -a
$ php ./example-11.php remove -r

위처럼 사용하는 것은 가능하지만, php ./example-11.php list -r은 에러를 냅니다.

어떤 서브커맨드를 사용했는지 확인하기 위해서는 Console_CommandLine_Result 인스턴스의 command_name 프로퍼티 값을 이용하면 됩니다. 또한, 서브커맨드에 사용된 옵션과 인자는 동일한 인스턴스의 command 프로퍼티를 이용합니다. command는 서브커맨드에 대한 Console_CommandLine_Result 인스턴스입니다.

사용자 정의

특별한 형식을 가진 옵션을 받는 경우가 종종 있습니다. 예를 들면 IP 같은 것 말입니다. 보통의 경우 IP를 문자열로 받은 후에 문자열이 올바른 IP인지 검사를 합니다만, PEAR Console_CommandLine에서는 Action을 사용자 정의하여 조금 더 깔끔하고 통일된 형태로 이를 처리 할 수 있습니다.

소스 코드를 설명드리기 전에 첫번째 글에서 보여드렸던 Composer로 PEAR 패키지를 설치하는 방식 대신에 Packagist를 통해 설치하는 것을 보여드리려고 합니다.

1
$ composer require pear/console_commandline

위의 명령은 다음과 같은 내용을 가진 composer.json을 생성합니다.

1
2
3
4
5
{
    "require": {
        "pear/console_commandline": "^1.2"
    }
}

composer를 통해 CommandLine을 설치했으면, 이제 옵션으로 IP를 입력받는 예제 소스 코드를 살펴 보도록 하겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

require 'vendor/autoload.php';

class ActionIP extends Console_CommandLine_Action
{
    public function execute($value=false, $params=array())
    {
        if (ip2long($value) === false) {
            throw new Exception('Invalid IP');
        }
        $this->setResult($value);
    }
}

Console_CommandLine::registerAction('StoreIP', 'ActionIP');

$parser = new Console_CommandLine();
$parser->addOption(
    'ip',
    array(
        'short_name'  => '-i',
        'long_name'   => '--ip',
        'description' => 'IP Address',
        'help_name'   => 'IP',
        'action'      => 'StoreIP'
    )
);

try {
    $result = $parser->parse();
    print_r($result->options);
} catch (Exception $e) {
    $parser->displayError($e->getMessage());
};

우선 액션을 정의하기 위해서는 Console_CommandLine_Action 클래스를 상속받은 클래스를 하나 정의해야 합니다. 여기서는 일관성을 위해 Action으로 시작하는 ActionIP 클래스를 정의 해 보겠습니다. 이 클래스는 publicexecute 메소드를 정의해야 합니다. execute 메소드는 옵션의 값을 첫번째 파라미터 $value로 넘겨줍니다. 이 value를 검사하고, 필요하다면 적절히 변형하여 $this->setResult($value)를 통해 값을 저장하면 내장된 다른 Store 계열 액션들과 마찬가지로 사용 할 수 있게 됩니다.

액션을 처리 할 클래스를 정의했다면 이 클래스를 Consol_CommandLine에 등록을 시켜주어야 합니다. 이를 처리하는 정적 메소드는 Console_CommandLine::registerAction으로 첫번째는 옵션을 정의 할 때 쓸 액션의 이름을 받고, 두번째는 액션을 정의한 클래스명을 받습니다.

이렇게 액션을 처리하는 클래스를 정의하고 이를 등록시켜 준 후, 내장된 액션들처럼 자유롭게 사용이 가능해집니다.

updatedupdated2021-01-042021-01-04