2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPでコマンドを実装してみる cp篇

Last updated at Posted at 2015-08-14

最近仕事してない気がする… と怯えはじめたtadsan氏は飽きるまでプログラミングの練習に励むことにしたのだった…

これまでのあらすじ

  1. PHPでコマンドを実装してみる かんたんecho篇
  2. PHPでコマンドを実装してみる cat篇
  3. PHPでコマンドを実装してみる かんたんseq篇

cpコマンドってなんだっけ

コマンドライン引数のパースはめんどくさいので、例によって省略することにする。

# ファイルaをファイルbにコピーする
# (ファイルaは存在しないとエラー、bはなくても良いが、bがディレクトリだったらエラー)
% cp a b

# ファイルaとファイルbをディレクトリDにコピーする
# (ファイルaとbが存在しないとエラー、Dがディレクトリじゃないとエラー)
% cp a b D

ちょっぴりめんどくさいぞー?

実装

ファイルをコピーするためのcopy函数が失敗したときの保険のためにエラーハンドラーをセットしておく。個人的には禁じ手に近いのだけれど…

bootstrap.php
<?php
namespace zonuexe;

if (!function_exists('\zonuexe\exception_error_handler')) {
    /**
     * @link http://php.net/manual/functions.user-defined.php
     */
    function exception_error_handler($severity, $message, $file, $line)
    {
        if (!(error_reporting() & $severity)) {
            return;
        }
        throw new \ErrorException($message, 0, $severity, $file, $line);
    }
    set_error_handler('\zonuexe\exception_error_handler');
}

あとは、ディレクトリを指定したときに./hoge/と書いても./hogeと書いても問題ないように正規化するやつ。

if (is_dir($dest)) {
    $pathinfo = pathinfo($dest);
    $dest = $pathinfo['dirname'] . '/' . $pathinfo['basename'];
}

完成

cp
#!/usr/bin/env php
<?php
namespace zonuexe\UnixCommand;
require_once dirname(__DIR__) . '/bootstrap.php';

/**
 * CP command
 *
 * @author    USAMI KENTA <tadsan@zonu.me>
 * @license   http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
 * @copyright 2015 USAMI Kenta
 */

if (realpath($_SERVER['SCRIPT_FILENAME']) == realpath(__FILE__)) {
    $argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [];
    array_shift($argv);

    exit(cp($argv));
}

/**
 * @return int UNIX status code
 */
function cp(array $argv)
{
    $failed = false;

    $len = count($argv);
    if ($len === 0) {
        fwrite(STDERR, 'cp: missing file operand' . PHP_EOL);
        return 1;
    }

    $dest = array_pop($argv);
    $is_dir = is_dir($dest);

    if ($len === 1) {
        $message = "cp: missing destination file operand after '{$dest}'";
        fwrite(STDERR, $message . PHP_EOL);
        return 1;
    }

    if (count($argv) > 1 && !$is_dir) {
        $message = "cp: target '{$dest}' is not a directory";
        fwrite(STDERR, $message . PHP_EOL);
        return 1;
    }

    if ($is_dir) {
        $pathinfo = pathinfo($dest);
        $dest = $pathinfo['dirname'] . '/' . $pathinfo['basename'];
    }

    foreach ($argv as $f) {
        if (is_dir($f)) {
            $message = "cp: omitting directory '{$f}'";
        } elseif (!is_file($f)) {
            $message = "cp: cannot stat '{$f}': No such file or directory";
        } elseif ($dest == $f) {
            $message = "cp: '{$f}' and '{$f}' are the same file";
        }

        if ($is_dir) {
            $new_file = $dest . '/' . basename($f);
        } else {
            $new_file = $dest;
        }

        if (!is_writable($dest)) {
            $message = "cp: cannot create regular file '{$new_file}': Permission denied";
        }

        if (isset($message)) {
            fwrite(STDERR, $message . PHP_EOL);
            $failed = true;
            continue;
        }

        try {
            copy($f, $new_file);
        } catch (\ErrorException $e) {
            $message = preg_replace('@\Acopy@', 'cp', $e->getMessage());
            fwrite(STDERR, $message . PHP_EOL);
            $failed = true;
            continue;
        }
    }

    return $failed ? 1 : 0;
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?