3
2

More than 1 year has passed since last update.

この処理Perlでどう書く?

Last updated at Posted at 2019-12-19

この処理Pythonでどう書く? の二番煎じです。

実際perlだったらどう書くかなと考えながら練習がてら。できる限りCOREモジュール縛り。

この処理Perlでどう書く?

標準出力・標準エラー出力

# 標準出力にメッセージ
echo 'HELLO'

# 標準エラーにメッセージ
echo 'ERROR!' >&2
use strict;
use warnings;

# 標準出力にメッセージ
print "HELLO\n";
# もしくは v5.10 以上を use した上で say
use v5.10;
say "HELLO";

# 標準エラーにメッセージ。(PBP p.235 「10.12 ファイルハンドルへの出力」に従う)
print {*STDERR} "ERROR!\n";
# もしくは warn を使う。最後に改行を追加するとwarnが通常付与する行番号の情報を消せる。
warn "ERROR!\n";

ファイル関係

パス操作

name="$(basename "/foo/bar/baz.txt")" # => baz.txt          ファイル名
name="$(dirname "/foo/bar/baz.txt")"  # => /foo/bar/        親ディレクトリパス
fullpath="/foo/bar/${name}"           # => /foo/bar/baz.txt パスの連結
use strict;
use warnings;
use File::Basename qw(basename dirname);
use File::Spec;

my $name = basename("/foo/bar/baz.txt");  # => baz.txt
my $dir = dirname("/foo/bar/baz.txt");  # => /foo/bar
my $fullpath = File::Spec->catfile("/foo/bar", $name);  # => /foo/bar/buz.txt

チルダや環境変数が含まれるパスを扱う

import pathlib

bad = pathlib.Path("~/Donwloads/report.txt") # ~ は自動展開されない!

f = pathlib.Path("~/Donwloads/report.txt").expanduser()
g = pathlib.Path("${WORK_DIR}/input.txt").expandvars()
use strict;
use warnings;

my $bad = "~/Downloads/report.txt";

# globを使う。ワイルドカードに注意。
my $f = glob '~/Downloads/report.txt';  # スカラコンテキストでアクセスする。

# %ENV から環境変数を取れる。
my $g = "$ENV{WORK_DIR}/input.txt";

ファイルの読み書き

import pathlib
path = pathlib.Path('foo.txt')

with open(path, 'r', encoding="utf-8") as f:
    for line in f:
        # line を処理

with open(path, 'w', encoding="utf-8") as f:
    print("内容", file=f)

with path.open('r') as f: # Pathのメソッドを使って開くこともできる。
    # ...
use strict;
use warnings;
use open qw/:encoding(UTF-8) :std/;
use utf8;
use autodie;

my $path = 'foo.txt';

READ: {
    open my $fh, '<', $path;

    while (<$fh>) {
        chomp(my $line = $_);
        # $line を処理
    }

    close $fh;
}

WRITE: {
    open my $fh, '>', $path;
    print {$fh} "内容";
    close $fh;
}

1;

行数を数える(wc -l

with path.open('rb') as f:
    count = sum(1 for line in f)
use strict;
use warnings;
use autodie;
open my $fh, '<:raw', $path;
my $count = () = <$fh>;  # リストコンテキストでファイルハンドルを取り、それにスカラコンテキストでアクセス。

ファイルの列挙

import pathlib
dir = pathlib.Path('/tmp')
for file in dir.glob('tmp.*'):
    # ファイルを処理する
    # fileは文字列ではなく、pathlib.Pathであることに注意
use strict;
use warnings;

for my $file (glob '/tmp/tmp.*') {
    # ファイルを処理する
    # $fileは文字列
}

ファイルの情報(存在確認・作成日時)

import pathlib
f = pathlib.Path('/bin/bash')
f.exists()  # 存在確認

f.is_file() # ファイル?
f.is_dir()  # ディレクトリ?

f.stat().st_ctime # 作成日時
f.stat().st_mtime # 更新日時
f.stat().st_atime # アクセス日時
use strict;
use warnings;
use File::stat;

my $f = '/bin/bash';

my $sb = stat($f);  # 特殊ファイルハンドル "_" がキャッシュされる。
-e _;  # 存在確認
-f _;  # ファイル?
-d _;  # ディレクトリ?

$sb->ctime;  # 作成日時
$sb->mtime;  # 更新日時
$sb->atime;  # アクセス日時

移動・削除

import pathlib

path_from = pathlib.Path('.bash_history')
path_to = pathlib.Path('/tmp/.bash_history')
path_from.rename(path_to) # 移動
path_from.unlink() # 削除
use strict;
use warnings;
use File::Copy qw(move);
use autodie qw(move);

my $path_from = '.bash_history';
my $path_to = '/tmp/.bash_history';
move $path_from, $path_to;  # 移動
unlink $path_from;  # 削除

コピー

import shutil
import pathlib

path_from = pathlib.Path('.bash_history')
path_to = pathlib.Path('/tmp/.bash_history')
shutil.copy2(path_from, path_to) # コピー
use strict;
use warnings;
use File::Copy qw(copy);
use autodie qw(copy);

my $path_from = '.bash_history';
my $path_to = '/tmp/.bash_history';
copy $path_from, $path_to;

外部コマンド

単純に実行する

import subprocess
subprocess.run(['sl', '-h'], check=True)
use strict;
use warnings;
use autodie qw(system);
system qw(sl -h);

外部のコマンドを実行し、標準出力を受け取る

import subprocess
r = subprocess.run(['echo', '世界'], check=True, stdout=subprocess.PIPE)
r.stdout # => b'\xe4\xb8\x96\xe7\x95\x8c\n' '世界\n'をUTF-8でエンコードしたもの
r.stdout.decode(sys.getfilesystemencoding()) # => '世界\n'
use strict;
use warnings;
use autodie;
use Encode;

# 実行するコマンドが安全なら
my $stdout = `echo 世界`;
$stdout;  # '世界\n'のバイナリ文字列。utf8フラグは無し。
decode_utf8($stdout);  # '世界\n'、utf8フラグあり。

# 安全ではないなら
my @cmd = qw(echo 世界);
open my $fh, '-|', @cmd;
$stdout = do { local $/; <$fh> };  # 丸呑み
close $fh;

# utf8フラグの事を気にしたくないのなら
{
    use utf8;
    use open qw/:encoding(UTF-8) :std/;
    open my $fh, '-|', @cmd;
    $stdout = do { local $/; <$fh> };  # utf8フラグあり
    close $fh;
}

# 古いシステムでロケールがUTF-8じゃない?正気か??
{
    no utf8;
    use Encode::Locale;  # non-core
    my @lcmd = map { encode(locale => decode_utf8($_)) } @cmd;
    open my $fh, '-|', @lcmd;
    $stdout = decode(locale => do { local $/; <$fh> });
    close $fh;
}

環境変数やカレントディレクトリを変更する

env = dict(os.environ) # Pythonスクリプトの環境変数をコピー
env['DB_HOST'] = 'localhost' # 環境変数を変更

cwd = pathlib.Path('/')

subprocess.run(['setup.sh'], cwd=cwd, env=env)
use strict;
use warnings;
use Cwd qw(chdir);  # chdir時に$ENV{PWD}をアップデートする
use autodie;

local $ENV{DB_HOST} = 'localhost';
my $orig_dir = Cwd::getcwd();

chdir "/";
system "setup.sh";
chdir $orig_dir;

リダイレクトを使う

import subprocess
import os.path

fi = open(os.path.expanduser('~/.bash_history'))
fo = open('p.txt', 'wb')
subprocess.run(['grep', 'python[23]'], stdin=fi, stdout=fo)
# p.txt に検索結果が出力される
# COREモジュール縛り
use strict;
use warnings;
use autodie;
use IPC::Open2;

open my $fh_history, '<', scalar glob '~/.bash_history';

my ($fh_out, $fh_in);
my $pid = open2($fh_out, $fh_in, 'grep', 'python[23]');
print {$fh_in} $_ while <$fh_history>;
close $fh_in;
close $fh_history;

open my $fh_txt, '>:raw', 'p.txt';
print {$fh_txt} $_ while <$fh_out>;
close $fh_txt;
close $fh_out;

waitpid $pid, 0;
my $return_code = $? >> 8;
# COREモジュール以外も使っていい場合
use strict;
use warnings;
use IPC::Run3 qw(run3);  # non-core

my $fi = scalar glob '~/.bash_history';
my $fo = 'p.txt';

run3 ['grep', 'python[23]'], $fi, $fo;

パイプを使う

p1 = subprocess.Popen(["cat", '.bash_history'], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["grep", "python"], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close()
output = p2.communicate()[0] # history から 'python' を含む行を検索した結果
p2.returncode # grep の終了コード
use strict;
use warnings;
use autodie;
use IPC::Open2;

open my $fh_history, '<', scalar glob '~/.bash_history';

my ($fh_out, $fh_in);
my $pid = open2($fh_out, $fh_in, 'grep', 'python');

print {$fh_in} $_ while <$fh_history>;

close $fh_in;
close $fh_history;

waitpid $pid, 0;
my $returncode = $? >> 8;
my $output = do { local $/; <$fh_out> };

close $fh_out;

spawn → wait (外部コマンドを起動し、終了を待つ)

import subprocess

p1 = subprocess.Popen(['/path/to/heavy-sub-process1'])
p2 = subprocess.Popen(['/path/to/heavy-sub-process2'])

p1.wait()
p2.wait()
use strict;
use warnings;

my @cmds = (
    ['/path/to/heavy-sub-process1'],
    ['/path/to/heavy-sub-process2'],
);

my @pids;
for my $cmd (@cmds) {
    my $pid = fork;
    if (!defined $pid) {
        die "Can't fork: $!";
    }
    elsif (!$pid) {
        exec @{$cmd};
        exit 1;
    }
    else {
        push @pids, $pid;
    }
}

my @rcs;
for my $pid (@pids) {
    waitpid $pid, 0;
    push @rcs, $? >> 8;
}

シェルを実行する

subprocess.run('echo Hello > /dev/null', shell=True)
system 'echo Hello > /dev/null';  # ">" のようなメタキャラクタがあればshellが呼ばれる

時刻関係

from datetime import datetime, timedelta # datetimeは日時を、timedeltaは経過時間を表す

epoch = datetime.now().timestamp()
# => 1540277405.379158 現在のUnix時刻(小数)

datetime.fromtimestamp(1540277405).strftime('%FT%T')
# => '2018-10-23T15:50:05'

start = datetime.now()
# 何か時間がかかる処理をする

duration = datetime.now() - start # datetime - datetime は timedelta
print(duration / timedelta(seconds=1)) # 経過時間を数値型にするには、別の timedeltaで割る
# => 42.680422 かかった秒数(小数)
print(duration.total_seconds()) # これでもOK
# => 42.680422 かかった秒数(小数)
use strict;
use warnings;
use v5.12;
use Time::Piece;
use Time::HiRes;

my $epoch = localtime->epoch;
# => 現在のUnix時刻(整数)

my $start = localtime;
# なにか時間がかかる処理をする

my $duration = localtime() - $start;  # Time::Piece - Time::Piece は Time::Seconds
say $duration->seconds;
# => かかった秒数(整数)

my $frac_epoch = Time::HiRes::time();
# => 現在のUnix時刻(小数)

my $frac_duration = Time::HiRes::time() - $frac_epoch;
say $frac_duration;
# => かかった秒数(小数)

文字列関係

文字列への式埋め込み

message='世界!'
print(f"Hello {message}") # => Hello 世界!
use strict;
use warnings;

my $message = '世界!';
print "Hello ${message}\n";  # => Hello 世界!
print(f"1 + 2 = {1 + 2}") # => 1 + 2 = 3
use strict;
use warnings;

# 文字列に埋め込めるようリファレンスをくぐらせる
print "1 + 2 = ${\(1 + 2)}\n";  # => 1 + 2 = 3

ヒアドキュメント

import textwrap

report = textwrap.dedent(f"""
    レポート
    日付: {date}
""")
use strict;
use warnings;
use v5.26;
use Time::Piece;

my $report = <<~EOF;  # 比較的新しいperlのdedent
    レポート
    日付: ${\(localtime->strftime("%Y年%m月%d日"))}
    EOF

コマンドライン引数

import sys
sys.argv # => ['a.py', 'input.txt', '-o', 'output.txt']
# プログラムファイル名自体は $0 に入っている。
@ARGV; # => ('input.txt', '-o', 'output.txt')
import argparse

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('integers', metavar='N', type=int, nargs='+',
                    help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')

args = parser.parse_args()
print(args.accumulate(args.integers))
#!/usr/bin/perl
use v5.12;
use strict;
use warnings;
use Getopt::Long qw(:config posix_default no_ignore_case gnu_compat permute);
use Pod::Usage qw(pod2usage);
use File::Basename qw(basename);
use List::Util qw(max sum);

# 無理やりpythonのargparseに寄せたので多少ぎこちないかも。

my %args = (
    accumulate => \&max,
);

GetOptions(
    'h|help' => sub { pod2usage(0) },
    sum => sub { $args{accumulate} = \&sum },
) or pod2usage(1);

for my $arg (@ARGV) {
    if ($arg =~ /\D/) {
        pod2usage(
            -exitval => 1,
            -msg => "argument N: invalid int value: $arg",
        );
    }
}

say $args{accumulate}->(@ARGV);

1;
__END__

=pod

=encoding UTF-8

=head1 NAME

test.pl -- blah blah

=head1 SYNOPSIS

test.pl [-h] [--sum] N [N ...]

Process some integers.

=head2 OPTIONS

=over 2

=item C<-h>

show this help message

=item C<--sum>

sum the integers (default: find the max)

=back

=cut

終了時の処理&シグナルをtrapする

import atexit
import os

tf = '/path/to/tempfile'

@atexit.register
def cleanup():
    os.remove(tf)
use strict;
use warnings;
use autodie;

my $tf = '/path/to/tmp';
$SIG{$_} = \&cleanup for qw( HUP INT QUIT TERM );

sub cleanup {
    unlink $tf;
}

HTTPリクエスト(curlやwgetの代替)

# URLを、GETでリクエストし、レスポンスボディを出力する
import urllib.request
import urllib.parse

params = urllib.parse.urlencode({'spam': 1, 'eggs': 2, 'bacon': 0})
url = f"http://www.musi-cal.com/cgi-bin/query?{params}"
with urllib.request.urlopen(url) as f:
     print(f.read().decode('utf-8'))
# URLを、GETでリクエストし、レスポンスボディを出力する
use strict;
use warnings;
use v5.12;
use HTTP::Tiny;

my %params = (spam => 1, eggs => 2, bacon => 0);
my $url = 'http://www.musi-cal.com/cgi-bin/query';
my $response = HTTP::Tiny->new->get($url, \%params);
die "Failed" unless $response->{success};
say $response->{content};
3
2
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
3
2