🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

解決済

2回答

1590閲覧

Rubyでtailコマンド

退会済みユーザー

退会済みユーザー

総合スコア0

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

0クリップ

投稿2020/12/19 11:59

編集2020/12/20 01:38

前提・実現したいこと

RubyでUnixのtailコマンドのようなメソッドを作りたいです。

発生している問題・エラーメッセージ

何も出力されません。

該当のソースコード

Ruby

1def tail(lines, filename) 2 File.open(filename) do |io| 3 io.seek(lines, IO::SEEK_END) 4 puts io.read 5 end 6end 7 8tail(5, __FILE__) 9

###追記

Ruby

1def tail(lines, filename) 2 File.open(filename) do |io| 3 chars_array = [] 4 5 io.seek(0, IO::SEEK_END) 6 while chars_array.count("\n") != lines 7 chars_array << io.read(1) 8 io.seek(-2, IO::SEEK_CUR) 9 end 10 11 p chars_array.reverse.join 12 end 13end 14 15tail(5, __FILE__)

補足情報

ruby 2.7.2p137です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

戻るなら負数を指定します。
io.seek(-lines, IO::SEEK_END)
ただ、バイト単位なので、tail -c相当ですね。

行単位にするなら、
案1:
先頭から順番に行を認識しながら読んで、直近のlines行を保存しつつ、最後まで読んで保存したものを表示
案2:
末尾から1バイトずつ遡って読んでいき(毎回seekする)、lines個目の改行を認識して、読んだ物を表示
案3:
その折衷案

#追記
質問追記に対しての改善。
1.末尾のバイトが欠ける、および、表示が\nから始まる点の対応

Ruby

1def tail(lines, filename) 2 File.open(filename) do |io| 3 chars_array = [] 4 5 io.seek(-1, IO::SEEK_END) 6 while chars_array.count("\n") < lines+1 7 chars_array << io.read(1) 8 io.seek(-2, IO::SEEK_CUR) 9 end 10 chars_array.pop 11 12 p chars_array.reverse.join 13 end 14end

2.さらに、大きすぎる行数を指定した場合の対応

Ruby

1def tail(lines, filename) 2 File.open(filename) do |io| 3 chars_array = [] 4 5 io.seek(-1, IO::SEEK_END) 6 while chars_array.count("\n") < lines+1 7 chars_array << io.read(1) 8 if io.pos == 1 9 chars_array << :DUMMY 10 break 11 end 12 io.seek(-2, IO::SEEK_CUR) 13 end 14 chars_array.pop 15 16 p chars_array.reverse.join 17 end 18end

3.最終行の末尾に\nが無いケースと、大きさゼロのファイルの対応
あわせて、少しスッキリさせる。

Ruby

1def tail(lines, filename) 2 File.open(filename) do |io| 3 io.seek(-1, IO::SEEK_END) rescue nil 4 chars_array = [io.read(1)] 5 n = 0 6 while io.pos >= 2 # 普通の「EOFでない間」に相当 7 io.seek(-2, IO::SEEK_CUR) 8 c = io.read(1) 9 if c == "\n" 10 n += 1 11 break if n >= lines 12 end 13 chars_array << c 14 end 15 16 p chars_array.reverse.join 17 end 18end

投稿2020/12/19 16:12

編集2020/12/20 11:55
otn

総合スコア85893

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/12/20 02:57 編集

回答ありがとうございます。案2を元に追記しました。 全体を読み込まずに処理する方法を探していたのでとても助かりました。 追記についてですが、pした結果は "\n p chars_array.reverse.join\r\n end\r\nend\r\n\r\ntail(5, __FILE__)\r" になるのですがprintすると print chars_array.reverse.join end end tail(5, __FILE__) と空行が挿入されます。 どうしても原因がわからないので教えていただきたいです。 また、IO::SEEK_ENDを第二引数に指定したseekの第一引数に正の数を指定するとどのように処理されるのでしょうか?
otn

2020/12/20 06:28

Windowsですかね。私がWindows10で、 p chars_array.reverse.join の後に print p chars_array.reverse.join を入れると、 ======================= "\n print chars_array.reverse.join\r\n end\r\nend\r\n\r\ntail(5, __FILE__)\r" print chars_array.reverse.join end end tail(5, __FILE__) ======================= と、特に異常は無いです。 改善点としては、考慮不足でファイルの最終バイト(通常は\n)が欠けることです。 > また、IO::SEEK_ENDを第二引数に指定したseekの第一引数に正の数を指定するとどのように処理されるのでしょうか? 位置は、「ファイル末尾+指定バイト」にずれますが、データが無いので読むとEOFです。
退会済みユーザー

退会済みユーザー

2020/12/20 06:40

windows10です。 たった今気づいたのですが、VScodeのCode Runnerで実行しているのが原因かも知れないです。
退会済みユーザー

退会済みユーザー

2020/12/20 08:28

すみません…一応少し改善したものを追記しましたが、最終バイトがかけることの解決方法が全くわかりません。 どこが間違っているかだけでも教えていただけないでしょうか?
退会済みユーザー

退会済みユーザー

2020/12/20 11:08

重ね重ね本当に申し訳ないです。追記について、seekの-1やlines+1、:DUMMYがなぜそうなるのかがよく理解できないので、解説していただきたです。
otn

2020/12/20 11:29 編集

> seekの-1 最後の文字を読むには、末尾に位置づけたらEOFなので、末尾マイナス1に位置づける必要があります。 >lines+1 5行表示するには、末尾から6つ目の\nの次から表示します。 今までは、末尾の\nをカウントしてなかったので、5で良かったのですが、本来は5つ目の\nの次から表示する必要があります。 > :DUMMY line+1個目の\nをpopで取り除く処理を入れたので、それと処理を合わせるために、ゴミを入れてます。このごみはbreak直後のpopで取り除かれます。 と、ここまで書いていて気づきましたが、最後のバイトが\nでないケースの考慮が漏れてました。
退会済みユーザー

退会済みユーザー

2020/12/20 11:52

なるほど、解説ありがとうございます。
otn

2020/12/20 11:58 編集

「読んだらすぐに追加して、最後に1つ取り除く」という処理をしないように、順番を入れ替えました。 heed コマンドを1文字ずつ読み込んで実現する場合のプログラム構造と同じになったと思います。
退会済みユーザー

退会済みユーザー

2020/12/20 12:28

丁寧な回答有難うございました。
guest

0

io.seek(lines, IO::SEEK_END)は
終端からlinesで指定されたバイト数分、前へ読み込み位置を進めます。
終端からlinesで指定された行数分、戻るわけではありません。
最後から指定された行数分を表示するなら、以下のようにしてください。
全行を読み込み、最後から指定された行数分を表示しています。

Ruby

1def tail(lines, filename) 2 File.open(filename) do |io| 3 line_arr = io.readlines 4 if line_arr.size < lines 5 sx = 0 6 else 7 sx = line_arr.size - lines 8 end 9 (sx...line_arr.size).each do|i| 10 print line_arr[i] 11 end 12 end 13end 14 15tail(5, __FILE__) 16

投稿2020/12/19 13:33

編集2020/12/19 14:00
tatsu99

総合スコア5493

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/12/20 00:39

回答ありがとうございます。 seekが移動する単位は行数だと思い込んでいました。 eachのiを使ってインデックスを指定するという方法も参考になります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問