Ruby more basics - シェル環境の拡充としての Ruby

[よりよい Perl, Awk, sed としての Ruby] [Perl正規表現]
[2014/06/30新規] [2014/06/30更新]

Contents

主な形式

ruby [-l] [-s] script_file [file...]

外部コマンドとしての利用の説明

Ruby はテキストフィルタとしての利用だけでなく、汎用なシステム操作記述言語としての利用に十分な組み込み関数や拡張モジュールが充実しており、シェル環境で足りない機能を外部コマンドとして拡充するだけの能力が備わっている。

Ruby スクリプトを外部コマンドとして利用する為には、Unix 環境では以下のような行頭から始めればよい。

#!/usr/bin/ruby
	:

また、コマンドラインオプションを指定するには、以下のように ruby -s のように起動すれば、そのコマンドに指定したオプションが変数として定義されるようになる。

#!/usr/bin/ruby -s
	:

拡張ライブラリの利用

Ruby には多様な拡張ライブラリが備わり、その上、RubyGems.org では数多のサードパーティ製拡張ライブラリが用意されている。インストール済みの拡張ライブラリを利用するには、以下のようにする。

#!/usr/bin/ruby
require 'etc'
	:

例題

代表的な Unix コマンドに相当する Ruby スクリプトを以下にあげる。

`echo`

Ruby では '-l' オプションで print メソッドで自動的に改行がなされ、$, = ' ' で配列やリストのフィールドセパレータがスペースになる。

#!/usr/bin/ruby -l
$, = ' '
print ARGV

このように、echo と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`true`

Ruby における終了コードは特に明示しない限り、正常終了である。

#!/usr/bin/ruby

このように、true と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`false`

Ruby における終了コードは 'exit' カーネルモジュール関数で指定できる。ちなみに、GNU Awk では可能だが、Awk では終了コードを制御できない。

#!/usr/bin/ruby
exit 1

このように、false と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`date`

#!/usr/bin/ruby -l -s
print $u ? Time.now.gmtime.strftime("%c") : Time.now.localtime.strftime("%c")

このように、date と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`dirname`

#!/usr/bin/ruby -l
ARGV[0] or abort "usage: dirname path"
print File.dirname(ARGV[0])

このように、dirname と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`basename`

#!/usr/bin/ruby -l
ARGV[0] or abort "usage: basename string [suffix]\n       basename [-a] [-s suffix] string [...]"
if (ARGV[1])
  print File.basename(ARGV[0], ARGV[1])
else
  print File.basename(ARGV[0])
end

このように、basename と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`logname`

#!/usr/bin/ruby -l
require 'etc'
name = Etc::getpwuid(Process::UID.rid).name || Etc.getlogin || Etc.getpwuid().name
print name

このように、logname と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`id`

#!/usr/bin/ruby -s
require 'etc'
gids, gnames = [], {}
if (!ARGV[0])
  $id = Process::UID.eid
  gids.push(Process::GID.eid)
  Process.groups.each {|group| gids.push(group) }
  gids.shift if ($G)
else
  if (ARGV[0] !~ /^\d+$/)
    $name, $id = Etc::getpwnam(ARGV[0]).values_at(0, 2)
    exit 1 if (!$name)
  else
    $id = ARGV[0].to_i
  end
  $name, $gid = Etc::getpwuid($id).values_at(0, 3)
  exit 1 if (!$name)
  gids.push($gid)
  while (grent = Etc::getgrent)
    gname, gid, members = grent.values_at(0, 2, 3)
    members = (members.map {|member| Etc::getpwnam(member)[2] }).find_all {|id| id == $id }
    gids.push(gid) if (members.length > 0)
  end
end
if ($G)
  $, = ' '
  if (!$n)
    print gids if (gids.length > 0)
  else
    print gids.map {|gid| Etc::getgrgid(gid)[0] } if (gids.length > 0)
  end
elsif ($g)
  if (!$n)
    print gids[0]
  else
    $gname = Etc::getgrgid(gids[0])[0]
    exit 1 if (!$gname)
    print $gname
  end
elsif ($u)
  if (!$n)
    print $id
  else
    $name = Etc::getpwuid($id)[0]
    exit 1 if (!$name)
    print $name
  end
else
  $name, $gid = Etc::getpwuid($id).values_at(0, 3)
  exit 1 if (!$name)
  $gname = Etc::getgrgid($gid)[0]
  exit 1 if (!$gname)
  printf "uid=%u(%s) gid=%u(%s) ", $id, $name, $gid, $gname
  print "groups="
  gids.length.times do |i|
    if (!gnames[gids[i]])
      gname = Etc::getgrgid(gids[i])[0]
      print "," if (i != 0)
      printf "%u(%s)", gids[i], gname
      gnames[gids[i]] = gname
    end
  end
end
print "\n"

このように、id と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`touch [-c] [-a] [-m] [-r pathname|-t [[CC]YY]MMDDhhmm[.SS]]`

#!/usr/bin/ruby -s
time = Time.now;
if ($r)
  stat = File::Stat.new($r);
else
  if ($t)
    if ($t =~ /(?:((?:\d{2})?\d{2}))?(\d{2})(\d{2})(\d{2})(\d{2})(?:\.(\d{2}))?$/)
      _CCYY, _MM, _DD, hh, mm, _SS = $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i;
      if (_CCYY)
	if (_CCYY <= 99)
	  _CCYY += (69 <= _CCYY) ? 1900 : 2000
	end
      else
	_CCYY = (Time.localtime(time))[5] + 1900
      end
      _SS = 0 if (!_SS);
      time = Time.local(_CCYY, _MM, _DD, hh, mm, _SS);
    end
  end
end
if ($a && $m || !$a && !$m)
  atime = $r ? stat.atime : time;
  mtime = $r ? stat.mtime : time;
elsif ($a)
  atime = $r ? stat.atime : time;
elsif ($m)
  mtime = $r ? stat.mtime : time;
end
ARGV.each do |$_|
  if (!$c && !File::exists?($_))
    f = open($_, 'w') rescue abort("create failure for #{$_}: #{$!}")
    f.close
    atime ||= mtime
    mtime ||= atime
  end
  atime ||= time
  mtime ||= time
  File::utime(atime, mtime, $_) rescue abort("touch failure for #{$_}: #{$!}")
end

このように、touch と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

`du [-H|-L] [-a|-s] [-d depth] [-k|-m|-g|-h] [file ...]`

`du` コマンドを実現するには 'opendir', 'readdir', 'closedir' 及び 'stat', 'lstat' を使えれば十分だ。Ruby にはそれに対応する 'Dir::entries', 'File::stat', 'File::lstat' がある。

#!/usr/bin/ruby
$units = {
  'K'	=>           1024, # KibiBytes
  'M'	=>      1024*1024, # MebiBytes
  'G'	=> 1024*1024*1024, # GibiBytes
}
$S = 1
while (ARGV.length > 0)
  if (ARGV[0] == '--') then ARGV.shift; break
  elsif (ARGV[0] == '-H') then $H = true
  elsif (ARGV[0] == '-L') then $L = true
  elsif (ARGV[0] == '-a') then $a = true
  elsif (ARGV[0] == '-s') then $s = true
  elsif (ARGV[0] == '-d' && 1 < ARGV.length) then ARGV.shift; $d = ARGV[0].to_i
  elsif (ARGV[0] == '-k') then $S = 2
  elsif (ARGV[0] == '-m') then $S = 2*1024
  elsif (ARGV[0] == '-g') then $S = 2*1024*1024
  elsif (ARGV[0] == '-h') then $h = true
  else break end
  ARGV.shift
end
def resize(size)
  if (!$h)
    (size/$S).ceil
  else
    if (!(size < $units['G']/512))
      p = 'G'
    elsif (!(size < $units['M']/512))
      p = 'M'
    else
      p = 'K'
    end
    s = size/($units[p]/512)
    p = 'B' if (s < 1)
    f = "%3.0f"
    f = "%3.1f" if (!(s < 1) && s < 10)
    sprintf(f, s) + p
  end
end
@total_sizes = [ 0.0 ]
def do_one(one)
  size = 0
  if (File::directory?(one) && ($L || (!$L && !File::symlink?(one))))
    return if (!(File::readable?(one) && File::executable?(one)))
    @total_sizes.push(0.0)
    Dir::entries(one).each do |entry|
      next if (entry == '..')
      next if (entry == '.')
      do_one("#{one}/#{entry}")
    end rescue warn("cannot opendir #{one}: #{$!}")
    size = @total_sizes.pop
    print resize(size), "\t", one, "\n" if ((!$s || ($s && @total_sizes.length-1 == 0)) && (!$d || !($d < @total_sizes.length-1)))
  else
    return if (!(stat=($L && File::exists?(one) ? File::stat(one) : File::lstat(one))))
    size = stat.blocks
    print resize(size), "\t", one, "\n" if ($a)
  end
  @total_sizes[@total_sizes.length-1] += size
end
unless (ARGV.length > 0)
  do_one('.')
  exit 0
end
while (ARGV.length > 0)
  do_one(ARGV[0])
  ARGV.shift
end

このように、du と同じ ruby スクリプトファイルは以上のようになる。

[Perl] [Ruby] [Python]

参考文献

  1. Ruby の組み込みライブラリ
  2. Ruby の標準添付ライブラリ
Written by Taiji Yamada <taiji@aihara.co.jp>