ディレクトリ内のファイルを読み込む順番、気にしたことありますか?
Rubyのファイル読み込み順についての考察です。
環境:ruby 2.3.1p112
macOS 10.13.6 (17G65)
#はじめに
Rubyでディレクトリ内のファイルを処理する際、Dir.globを使います。
カレントのinディレクトリに
[txtファイル20個]
と
[その他のうんこファイル1万個]
があるとします。
txtファイルだけを処理したい場合はこうです。
Dir.glob('./in/*.txt').each do |fn|
puts fn
end
で結果はこうです。
./in/180726_152833_118036_016.txt
./in/180726_152833_118673_017.txt
./in/180726_152833_111733_006.txt
./in/180726_152833_116294_013.txt
./in/180726_152833_119965_020.txt
./in/180726_152833_113602_009.txt
./in/180726_152833_109057_002.txt
./in/180726_152833_116926_014.txt
./in/180726_152833_119551_019.txt
./in/180726_152833_114285_010.txt
./in/180726_152833_109710_003.txt
./in/180726_152833_115679_012.txt
./in/180726_152833_112946_008.txt
./in/180726_152833_111092_005.txt
./in/180726_152833_115039_011.txt
./in/180726_152833_107647_001.txt
./in/180726_152833_112327_007.txt
./in/180726_152833_119093_018.txt
./in/180726_152833_117478_015.txt
./in/180726_152833_110460_004.txt
勘の鋭い方はお気づきかもしれませんが、
./inディレクトリにあるtxtファイルのファイル名は
「日付_時刻_マイクロ秒_番号.txt」
すなわち
「ファイルが生成された時刻がファイル名」
になっています。
ということは、、、、
#ファイルが生成された順番に処理されてないじゃん!
順番に処理しなきゃなんない瞬間(とき)ってあるんだよ。
お父さんびっくりだよ…。
#何はともあれgoogle
glob で取得したファイル一覧の順番は保証されるのか
ここによると
Ruby(2.4.2)順番は保証されるかは…不明…。
Does Dir.glob guarantee the order?
海外の方も気にしておられるようですが、OSによるよ!とか。悲しい…。
#あんた、あの娘の何なのさ
じゃあDir.globの順番ってなんなのさ!この泥棒猫!ハアハア。
といいたいところですが、グッとこらえてgoogle。
フォルダー名の読み込み順番について
に
ls -f で実際に取得した順番で表示されます。
との回答がありました。
ではでは
ls -f ./in
. 180726_152833_114285_010.txt
.. 180726_152833_109710_003.txt
180726_152833_118036_016.txt 180726_152833_115679_012.txt
180726_152833_118673_017.txt 180726_152833_112946_008.txt
180726_152833_111733_006.txt 180726_152833_111092_005.txt
180726_152833_116294_013.txt 180726_152833_115039_011.txt
180726_152833_119965_020.txt 180726_152833_107647_001.txt
180726_152833_113602_009.txt 180726_152833_112327_007.txt
180726_152833_109057_002.txt 180726_152833_119093_018.txt
180726_152833_116926_014.txt 180726_152833_117478_015.txt
180726_152833_119551_019.txt 180726_152833_110460_004.txt
ほ、ほんまや!
#これは何の順番?
先程のTeratailの回答には「物理エントリ」順との回答もあります。
ページテーブル
ストレージに記憶(ページング)されている順ということかな…。
計算機的にはその方がいいのかもしれんが、人間にとっては困るんだyo!
#####じゃあDir:glob使えねーじゃん
#初心に帰り、ls -aを使ってみる
ということで一旦Dir.globから脱退してみましょう。
globeから脱退したYOSHIKIのように。
files = `ls -a ./in`.split("\n")
files.each do |f|
puts f if /.*\.txt$/ =~ f
end
Dir.globがls -fで取得したファイル順だっつーのなら、逆転の発想。
バッククォート記法でls -aして取得したファイルをsplitして配列化してeachします。
180726_152833_107647_001.txt
180726_152833_109057_002.txt
180726_152833_109710_003.txt
180726_152833_110460_004.txt
180726_152833_111092_005.txt
180726_152833_111733_006.txt
180726_152833_112327_007.txt
180726_152833_112946_008.txt
180726_152833_113602_009.txt
180726_152833_114285_010.txt
180726_152833_115039_011.txt
180726_152833_115679_012.txt
180726_152833_116294_013.txt
180726_152833_116926_014.txt
180726_152833_117478_015.txt
180726_152833_118036_016.txt
180726_152833_118673_017.txt
180726_152833_119093_018.txt
180726_152833_119551_019.txt
180726_152833_119965_020.txt
おおっ!ファイル生成順にテキストが処理されているっぽい????
くれないだー!
#はて?このままでいいのか?
ここで1つの疑念が生じます。
#####**ファイル名順に処理しているのではないのか?**と。
というわけで、ファイル名の先頭にランダムで3文字のアルファベットを付加してみましょう。
んで、さっきと同じ
files = `ls -a ./in`.split("\n")
files.each do |f|
puts f if /.*\.txt$/ =~ f
end
(さっきとはファイル生成時刻は違うのでファイル名も違いますよ)
AFC_180726_160348_137270_016.txt
BBU_180726_160348_133355_006.txt
CRV_180726_160348_135485_012.txt
DEU_180726_160348_132165_003.txt
FEX_180726_160348_132962_005.txt
FKQ_180726_160348_132576_004.txt
FWJ_180726_160348_138088_018.txt
IRY_180726_160348_134186_008.txt
KMK_180726_160348_134531_009.txt
KQG_180726_160348_136848_015.txt
LCY_180726_160348_133776_007.txt
OXJ_180726_160348_135874_013.txt
RHE_180726_160348_136242_014.txt
SWQ_180726_160348_135171_011.txt
TMT_180726_160348_138645_019.txt
VUA_180726_160348_137680_017.txt
VVM_180726_160348_134863_010.txt
WCV_180726_160348_139066_020.txt
ZSH_180726_160348_131737_002.txt
ZYD_180726_160348_131238_001.txt
案の定かよ!
#####ls -aはファイル名順に読み込むようです。
#ls -atrではどうよ!
↓ここを参考
あるディレクトリ下の全ファイルを更新日時の古い順に詳細表示
lsには色んなオプションがあるんだけど、
-atrは「t:更新順」「r:昇順」に並べ替えします。
files = `ls -atr ./in`.split("\n")
files.each do |f|
puts f if /.*\.txt$/ =~ f
end
これでどうだ!
ZYD_180726_160348_131238_001.txt
ZSH_180726_160348_131737_002.txt
DEU_180726_160348_132165_003.txt
FKQ_180726_160348_132576_004.txt
FEX_180726_160348_132962_005.txt
BBU_180726_160348_133355_006.txt
LCY_180726_160348_133776_007.txt
IRY_180726_160348_134186_008.txt
KMK_180726_160348_134531_009.txt
VVM_180726_160348_134863_010.txt
SWQ_180726_160348_135171_011.txt
CRV_180726_160348_135485_012.txt
OXJ_180726_160348_135874_013.txt
RHE_180726_160348_136242_014.txt
KQG_180726_160348_136848_015.txt
AFC_180726_160348_137270_016.txt
VUA_180726_160348_137680_017.txt
FWJ_180726_160348_138088_018.txt
TMT_180726_160348_138645_019.txt
WCV_180726_160348_139066_020.txt
おおやったね!
#結論
Dir.globはファイル更新順には読み込まないので、バッククォート記法でls -atrでやる
(Dir.globにオプションがあればいいのに...)
#投稿後にコメントを受けた結論
ここまでやってアレですが、投稿後、knuさんにコメントをいただきました。
名前の辞書順: Dir.glob(pattern).sort
生成順: Dir.glob(pattern).sort_by { |fn| File.birthtime(fn) }
更新順: Dir.glob(pattern).sort_by { |fn| File.mtime(fn) }
のように自分でソートする、が答えです。
え!俺アホやん...。
files = Dir.glob('./in/*.txt').sort_by { |fn| File.birthtime(fn) }
files.each {|fn| puts fn}
ZYD_180726_160348_131238_001.txt
ZSH_180726_160348_131737_002.txt
DEU_180726_160348_132165_003.txt
FKQ_180726_160348_132576_004.txt
FEX_180726_160348_132962_005.txt
BBU_180726_160348_133355_006.txt
LCY_180726_160348_133776_007.txt
IRY_180726_160348_134186_008.txt
KMK_180726_160348_134531_009.txt
VVM_180726_160348_134863_010.txt
SWQ_180726_160348_135171_011.txt
CRV_180726_160348_135485_012.txt
OXJ_180726_160348_135874_013.txt
RHE_180726_160348_136242_014.txt
KQG_180726_160348_136848_015.txt
AFC_180726_160348_137270_016.txt
VUA_180726_160348_137680_017.txt
FWJ_180726_160348_138088_018.txt
TMT_180726_160348_138645_019.txt
WCV_180726_160348_139066_020.txt
なるほど〜。これでスッキリ!