はじめに
こちらの記事でls
コマンドを学習中の言語で実装してみるといい練習になるという内容があったので、試してみました。
既存機能を自分で実装するのはロジックを組む練習にいいですね。
この記事が役に立つ方
- ロジックを組むことに慣れていない
- Ruby初心者
この記事のメリット
-
ls -x
コマンドを再現出来る - ロジックを考える練習になる
環境
- OS: macOS Mojave version 10.14.6
- シェル: zsh
- 使用言語: Ruby 2.6.3
ls -x
コマンドがやっていること
以下がls -x
コマンドの機能です。これを練習として、自作してみます。
- カレントディレクトリ内のファイル・ディレクトリ一覧を取得
- 取得した一覧から、デフォルトで非表示のもの(’.’や ‘..’など)を間引く
- 間引いた一覧を繰り返し処理で出力していく。出力時のルールは以下3点
- ファイル名・ディレクトリ名は見やすいように折返し表示にしない
- 表示幅は「最大のファイル・ディレクトリ名の長さ」+半角スペース1つ
- 名前の頭を昇順ソートし、Z字の順に表示
ターミナルの幅・サンプルファイル名
作成するプログラム名:ls_command.rb
ターミナルの幅:50
サンプルファイル・ディレクトリ:以下(ls -1
で出力)
banana8.txt
banana9.txt
ls_command.rb
orange #ディレクトリ
ruby #ディレクトリ
ruby4.txt
ruby5.txt
ruby6.txt
ruby7.txt
sample #ディレクトリ
sample1.txt
sample2.txt
sample3.txt
プログラム
自分で考えた結果がこちらです。
先日学んだ%による幅指定も活用しています。
こういうところで使うんですね。
children = Dir::entries(".").sort
# 文字数の最大値を算出。
string_length_max = 0
children.each do |n|
if string_length_max <= n.length
string_length_max = n.length
end
end
# ターミナルの幅 を 文字数の最大値+1 で割ることで、見やすく整形する準備を行う。
p column = (`tput cols`).to_i / (string_length_max + 1)
# 不要なファイルを配列から削除。
without_list = [".", ".."]
new_children = children - without_list
# 実表示部分。
new_children.each_with_index do |item, index|
print "%-#{string_length_max}s " % [item]
case (index + 1) % column
when 0
print "\n"
end unless index == 0
end
出力結果
うまくいきました!
❯ ruby ls_command.rb
banana8.txt banana9.txt ls_command.rb
orange ruby ruby4.txt
ruby5.txt ruby6.txt ruby7.txt
sample sample1.txt sample2.txt
sample3.txt
参考:ls -x
コマンドでの出力
❯ ls -x
banana8.txt banana9.txt ls_command.rb
orange ruby ruby4.txt
ruby5.txt ruby6.txt ruby7.txt
sample sample1.txt sample2.txt
sample3.txt
悔しいポイント
1. ls
コマンドが再現出来ていない
ls -x
コマンド:「Z字」の順にファイル・ディレクトリ名が出力される(左上→右上→左下→右下)
ls
コマンド:「N字の左右反転」順に出力される(左上→左下→右上→右下)
どうやったら縦にならびつつ、列方向にも出力できるのか?
2. 動作が遅い
通常のls -x
よりも動作がワンテンポ遅く、どこの部分が速度を落としているのか?
each_with_index
の中にcase
を入れてしまっているなど、条件分岐が多いからでしょうか。このあたりはまだ全然わかりません。
おわりに
いつもお世話になっているls
ひとつでも自分で考えると大変でした。
いい勉強。
小さなプログラムのロジックであればパッと頭に浮かんでくるよう、今後も継続して学習していきます!
参考にさせて頂いたサイト(いつもありがとうございます)
Rubyでターミナルのサイズを取得する - Qiita
配列の複数要素の削除はdelete_ifかreject、-なんかを使おう - Qiita
【追記】 リファクタリング後のls -x
17行→6行
@scivolaさんのアツいコメントを受け、早速修正してみたので追記します。
なんと、11行も間引くことができました!
元の自分のコードがなんて冗長だったのかと猛省です。
リファクタリングでこんなにも簡素化できるんですね。
map
やjoin
の使い方は特に応用が効きそうなので、今後も意識的に「使えないか?」という疑いを持っていきます。
children = Dir::children(".").sort
# 文字数の最大値を算出。
string_length_max = children.map{ |child| child.length }.max
# ターミナルの幅 を 文字数の最大値+1 で割ることで、見やすく整形する準備を行う。
column = (`tput cols`).to_i / (string_length_max + 1)
# 実表示部分。
children.each_slice(column) do |items|
puts items.map{ |item| "%-#{string_length_max}s " % item }.join
end