Header

  1. View current page

    kirrie's library

Profile_image?t=1225040663&type=small
2

쉘 스크립트로써의 PHP cli

쉘 스크립트로써의 PHP cli#

개요#

리눅스 시스템에서 명령행 기반의 스크립트는 운영체제의 사용을 편리하게 해줍니다. 단 기존의 쉘 스크립트는 (흔히 사용하는) 배쉬 쉘 스크립트나 펄 스크립트나, 파이썬 스크립트등을
이용하고 있으므로, PHPer들에게 있어서 진입장벽이 높았던 것은 사실입니다. (배쉬 쉘이나 펄, 파이썬등을 배워야 했으므로) 옛 버전의 PHP 인터프리터들은 안정성이나 편의성등이 부족하여 쉘 스크립트계(?)에서 외면 당해 온 것이 사실이나, 4.3.0버전 이후로 새로 도입된 PHP cli(PHP Command Line Interface)은 쉘 스크립트를 작성하려는 PHPer의 요구를 충분히 반영하고 있다고 생각합니다.
이에 몇 가지 쉘 스크립트로써의 PHP cli에 대한 내용을 소개할까 합니다.

표준 입/출력/에러#

먼저 표준 입/출력/에러에 대한 내용은 아래의 링크를 참조하시거나 리눅스 레퍼런스북을 참조하시기 바랍니다.

http://docs.hp.com/ko/B2355-90167/ch03s03.html
http://wiki.kldp.org/wiki.php/BashProgIntroHOWTO

PHP cli는 다음의 방법들로 쉽게 각 표준 스트림들을 열게 합니다.

  1. 방법 1.
    $stdin = fopen("php://stdin", "r"); //표준 입력은 읽기 전용입니다.
    $stdout = fopen("php://stdout", "w"); //표준 출력은 쓰기 전용입니다.

필요하다면 방법 1을 사용해야겠지만, 간편하게 미리 지정된 상수를 파일 핸들러로 사용할 수 있습니다.

  1. 방법 2.
    표준입력 STDIN
    표준출력 STDOUT

명령행 인자 사용#

마치 웹에서 GET이나 POST등을 이용해 스크립트에 정보를 전달하는 것처럼 명령행에서도 PHP 스크립트에 정보를 전달해야 할 필요가 있습니다.

  1. > php some_script.php first_param second_param

각 인자는 공백으로 구분하므로 하나의 인자는 반드시 공백없이 처리되어야 합니다. 위 명령에서 인자로 주어진 first_param과 second_param은 다음의 변수에 배열로 저장됩니다.

  1. $argv ($_SERVER['argv']도 같은 내용을 갖습니다.)

이때 배열의 0번 인덱스에는 스크립트 파일명이 저장되고, 인자는 1번 인덱스부터 차례대로 저장됩니다.

  1. 예제 1.
    > php some_script.php first_param second_param

    <?
    print_r($_SERVER['argv']); //슈퍼전역변수를 사용하는 것은 언제나 좋은 습관입니다. (단, 이놈의 변수명이 버전마다 자주 바뀐다는게 문제지만..)
    ?>

    Array
    (
        [0] => some_script.php
        [1] => first_param
        [2] => second_param
    )

이렇게 저장된 인자를 이용해 스크립트의 반응을 여러가지로 지정할 수 있습니다.

#

PHP 쉘 스크립트를 좀 더 유연하게 만드는 몇가지 팁을 소개합니다.

커맨드 라인에 명시적으로 PHP를 지정하지 않고 스크립트 실행하기#

일반적으로 PHP 쉘 스크립트를 실행하기 위해선 다음과 같은 명령을 내려야 합니다.

  1. > php some_script.php

매번 이러기는 귀찮지요. 다음과 같은 구문을 some_script.php의 맨 첫줄에 추가하고 실행권한을 줍니다. (chmod 755 some_script.php)

  1. #!/usr/bin/php
    <?
    ...
    ?>

    > ./some_script.php

배쉬 쉘은 (다른 쉘은 어떤지 모르겠습니다만) 일반적으로 자기 자신이 스크립트를 해석해서 실행하려고 합니다만, 첫줄의 #!/usr/bin/php은 배쉬 쉘에게 이하의 스크립트를 해석할 인터프리터를 지정하도록 합니다. #! 다음의 경로는 시스템에 따라 달라질 수 있습니다.

제한된 인자만 받기#

다음은 메뉴얼에 있는 내용입니다.

  1. #!/usr/bin/php
    <?php
    if ($argc != 2 || in_array($argv[1], array('--help', '-help', '-h', '-?'))) {
    ?>
    This is a command line PHP script with one option.
    Usage:  <?php echo $argv[0]; ?> <option>
    <option> can be some word you would like  to print out.
    With the --help, -help, -h,  or -? options,
    you can get this help.
    <?php
    } else {
       echo $argv[1];
    }
    ?>

$argc ($_SERVER['argc']) 에는 입력된 인자의 개수가 저장됩니다. if문에서 인자의 개수가 2개가 아니고 (즉, 2개의 인자는 인정하지 않겠다는 의미지요. $argc < 2가 되어야 할텐데, 메뉴얼이 좀 잘못됐네요.), 인자의 내용이 --help, -help, -h, -?이면 현재 스크립트의 사용법을 출력하고, 인자가 하나이면서 in_array에 해당되지 않는 인자면 그대로 출력하라는 의미입니다.
이걸 응용하면 원하는 인자만 받고, 그 외의 경우 Invalid Parameters나 사용법을 출력하게 할 수도 있습니다.

Prefix가 붙은 인자 받기#

어떤 PHP 쉘 스크립트가 파일명과 해당 파일명에 대한 처리 방법을 입력받아 그대로 수행한다고 합시다.

  1. > some_script.php file_name delete

그러나 여러 다른 스크립트에서 사용자 편의를 위해 인자의 순서를 사용자에게 일임하는 경우가 있습니다.

  1. > some_script.php file_name delete || > some_script.php delete file_name

그런데 어떤 인자가 파일이고 어떤 인자가 해당 파일에 대한 처리 방법인지 모호합니다. (인자 가운데 정해진 처리 방법, 즉 delete, copy, symlink등을 일일이 체크 할 수도 있습니다만 여기서는 다른 방법으로 처리하겠습니다.) 이때에 명시적으로 인자들 앞에 Prefix를 주도록 유도해서 인자를 명확하게 할 수 있습니다.

  1. > some_script.php -file file_name -action delete

이 경우 -file의 Prefix에 file_name이 지정된 것을 PHP로 하여금 어떻게 인식하도록 할까요? 다음과 같이 하면 됩니다.

  1. #!/usr/bin/php
  2. <?
  3. for($i = 1; $i <= count($_SERVER['argv']); $i+=2) {
  4.  
  5. switch($_SERVER['argv'][$i]) {

  6. case '-file' :

  7. $file_name = $_SERVER['argv'][$i + 1];

  8. break;

  9. case '-action':

  10. $action = $_SERVER['argv'][$i + 1];

  11. break;

  12. }

  13.  
  14. }
  15. echo "file_name => ".$file_name." || action => ".$action."\n";
  16. ?>

먼저 $i의 초기값이 1인 것은 $_SERVER['argv']의 0번째 인덱스에 스크립트 파일명이 들어가기 때문입니다. 증가값은 2로 합니다. 루프를 돌면서 공백으로 구분된 인자가 '-file'인 경우 그 다음 배열 인덱스에 저장된 인자는 당연히 file_name이므로 $file에 $_SERVER['argv'][$i + 1]의 값을 저장하도록 합니다. 증가값이 2인 이유는 Prefix와 Prefix에 대한 값을 한쌍으로 인식하도록 하기 위함입니다.

다른 방법도 있습니다.

  1. > some_script.php -f=file_name -a=delete

이 경우 인자의 해석은 다음과 같습니다.

  1. #!/usr/bin/php
  2. for($i = 1; $i < count($_SERVER['argv']; $i++) {
  3.  
  4. switch($substr($_SERVER['argv'][$i], 0, 3)) {

  5. case '-f=' :

  6. $file_name = substr($_SERVER['argv'][$i], 3, strlen($_SERVER['argv'][$i]));

  7. break;

  8. case '-a=':

  9. $action = substr($_SERVER['argv'][$i], 3, strlen($_SERVER['argv'][$i]));

  10. break;

  11. }

  12.  
  13. }

예제를 빠르게 작성하느라 Prefix를 -f나 -a등으로 단순화 시켰습니다만, strpos 함수를 이용하면 Prefix의 길이에 상관없이 설정할 수 있습니다.

프로그레스 바 표현하기#

wget은 http://나 ftp://프로토콜을 사용하는 강력한 명령행 다운로드 프로그램입니다. wget을 통해 어떤 파일을 다운로드 하면 중간에 다운로드 되는 상태를 확인할 수 있습니다.

  1. > wget http://kirrie.mixnut.co.kr/index.php
    --19:31:30--  http://kirrie.mixnut.co.kr/index.php
               => `index.php.1'
    Resolving kirrie.mixnut.co.kr... 210.109.103.146
    접속 kirrie.mixnut.co.kr|210.109.103.146|:80... 접속됨.
    HTTP request sent, awaiting response... 200 OK
    Length: 20 [text/html]

    100%[====================================>] 20            --.--K/s            

    19:31:30 (2.53 MB/s) - `index.php.1' saved [20/20]

이런게 PHP 쉘 스크립트에서도 작동한다면 멋지겠지요?

  1. #!/usr/bin/php
  2. <?
  3. $progress_bar = "\r[";
  4.  
  5. for($i = 0; $i <= 10; $i++) {
  6.  
  7. $percent = ($i * 10)."%";

  8.  

  9. for($j = (9 - $i); $j >= 0; $j--) $space .= " "; //$i값이 증가할수록 줄어드는 공백을 생성하기 위한 구문입니다.

  10. $space = str_pad("", (10 - $i), STR_PAD_RIGHT); // str_pad 함수를 잊고 있었네요.

  11.  
  12. //예를 들기 위해서 fwrite와 echo를 이용한 구문을 둘 다 적었습니다.

  13. fwrite(STDOUT, $progress_bar.$space."] ".$percent);

  14. echo $progress_bar.$space."] ".$percent;

  15.  
  16. $space = "";

  17. $progress_bar .= "=";

  18.  
  19. sleep(1);

  20.  
  21. }
  22. echo "\n";
  23. ?>

  24. > some_script.php
    [===========] 100%
    >

\r에 주의하시기 바랍니다. 캐리지 리턴을 사용하므로 해서 매 출력마다 열린 라인의 맨 앞으로 이동합니다. 또한 fwrite(STDOUT.. 이나 echo, print 등은 모두 동일하게 표준 출력을 이용합니다. echo가 좀 더 간단하겠지요.

인터렉티브한 명령행 스크립트 작성하기#

매번 스크립트에 인자를 넘겨주기 위해 열심히 키보드를 두드릴 필요가 없습니다. 간단히, 인터렉티브하게 2개의 숫자를 받아서 그 둘을 더하는 스크립트를 생각해 보도록 하겠습니다.

  1. #!/usr/bin/php
  2. <?
  3. echo "Input First Number> ";
  4. $first_num = fgets(STDIN);
  5. echo "Input Second Number> ";
  6. $second_num = fgets(STDIN);
  7. echo "Plus = ".($first_num + $second_num)."\n";
  8. ?>
  9.  
  10. > some_script.php
  11. Input First Number> 1<enter>
  12. Input Second Number> 2<enter>
  13. Plus = 3
  14. >
  15. ※ <enter>는 키보드의 enter키를 누른 상태입니다.

fgets는 아시다시피 열려진 파일 핸들러에서 하나의 라인 (\n로 끝나는 한 라인) 을 읽어옵니다. $stdin = fopen("php://stdin", "r"); 해도 같은 효과를 얻겠지만 간편하게 지정된 상수인 STDIN을 사용하는게 좋겠지요.

첨부 : 간이 mysql client#

php_cli_mysql_client.zip

간단하게 mysql client를 만들어 봤습니다. 물론 mysql에서 자체 제공하는 것 보다야 좋지 않습니다. 그냥 이런 가능성도 있구나 하고 봐주심 ㄱㅅㄱㅅ.

주의사항#

  1. 스크립트상의 오류로 에러가 발생하면 꽤나 지저분하게 보입니다. 최대한 에러가 나지 않도록 사전에 핸들링 하는게 필요합니다.
  2. 파일 관련 함수 사용시는 주의하시기 바랍니다. 웹서버의 모듈로 작동하는 PHP 스크립트는 웹서버의 권한으로 작동합니다. 다만, 쉘 스크립트로써 작동하는 PHP 스크립트는 실행자의 권한으로 실행되므로 root 권한으로 동작시에는 무제한으로 파일을 삭제해버릴지도 모릅니다. (스크립트가 어떤 파일들을 삭제하도록 코딩된 경우)
  3. 거의 대부분의 PHP 함수를 그대로 사용할 수도 있습니다. DB 접속도 가능하므로 잘하면 커스터마이즈된 명령행 mysql client를 만들 수도 있겠지요.
  4. 윈도우즈의 경우 동작하지 않는 함수들이 있으므로 주의하시기 바랍니다.
  5. 윈도우즈의 경우 배치파일을 이용하여 스크립트를 작성할 수 있습니다. 즉, some_script.php가 있다고 할때 some_script.bat파일의 내용은 @c:\php\cli\php.exe some_script.php %1 %2 %3 %4 등으로 하여서 동작할 수 있습니다. (라고 메뉴얼에 나와 있네요.)

History

Last edited on 03/10/2008 23:43 by kirrie

Comments (0)

You must log in to leave a comment. Please sign in.