Edited at

glob で取得したファイル一覧の順番は保証されるのか

More than 1 year has passed since last update.


glob とは?

Wikipediaより引用


グロブ(英: glob)とは主にUnix系環境において、ワイルドカードでファイル名のセットを指定するパターンのことである。


例えば BSD 版 ls コマンドで次のようにワイルドカード * を指定すると、マッチしたファイルのパスが出力されます。

% ls files/*.txt

files/1.txt files/4.txt files/8.txt
files/10.txt files/5.txt files/9.txt
files/2.txt files/6.txt
files/3.txt files/7.txt

Ruby ではこの順番が常に保証されていると勘違いしてハマってしまったので、これを機にいくつかの言語で順番が保証されているか調べてみました。


結果

結果は次の通りです。

言語
バージョン
保証されるか

Ruby
2.4.2
不明

Perl
6.18.2
指定フラグによる(デフォルト保証)

Python
3.6.3
不明

PHP
7.1.7
指定フラグによる(デフォルト保証)

Go
1.9.2
不明

不明なもので順番の保証を期待する場合は、明示的にソートを行う必要がありそうです。


準備

各言語で glob の出力結果を確認するために、次のようにファイルを作成しておきます。

% mkdir files

% touch files/{1..10}.txt

以降、各言語でのドキュメントと簡単なサンプルコードによる出力結果を確認します。


Ruby(2.4.2)

Dir.glob には言及がありませんでした。

「取得後に明示的にソートする必要がある」というページを見つけましたが、順番を保証しているかどうかは不明でした。

確認コードです。

Dir.glob("files/*").each{|i| puts i}

# 出力:
#
# files/10.txt
# files/9.txt
# files/8.txt
# files/5.txt
# files/4.txt
# files/6.txt
# files/7.txt
# files/3.txt
# files/2.txt
# files/1.txt


Perl(6.18.2)

glob は内部的にFile::Glob を呼んでいます(参考)。

File::Glob は POSIX フラグを指定可能で、これによって表示順の保証が変わります。

フラグ
保証されるか

GLOB_NOSORT
されない

GLOB_ALPHASORT
される

指定なし
される

まず、 GLOB_NOSORT が指定されているとソートは行われません(順番は保証されない)。

次に GLOB_ALPHASORT が指定されていると、大文字小文字を区別しないアルファベット順のソートが行われます(順番は保証される)。

また、フラグが未指定の場合は GLOB_CSH が設定されます。GLOB_CSHGLOB_BRACE | GLOB_NOMAGIC | GLOB_QUOTE | GLOB_TILDE | GLOB_ALPHASORT の指定になるので、結果的に大文字小文字を区別しないアルファベット順のソートが行われます(順番は保証される)。

確認コードです。

my @all_files = glob "files/*";

print join("\n", @all_files) . "\n\n";

# 出力:
#
# files/1.txt
# files/10.txt
# files/2.txt
# files/3.txt
# files/4.txt
# files/5.txt
# files/6.txt
# files/7.txt
# files/8.txt
# files/9.txt
#

ちなみに glob には POSIX フラグを指定できないので、次のように直接 bsd_glob を呼び、そのパラメータとしてフラグを指定します。

use File::Glob ':bsd_glob';

my @all_files = bsd_glob("files/*", GLOB_NOSORT);
print join("\n", @all_files) . "\n\n";

# 出力:
# files/10.txt
# files/9.txt
# files/8.txt
# files/5.txt
# files/4.txt
# files/6.txt
# files/7.txt
# files/3.txt
# files/2.txt
# files/1.txt
#


Python(3.6.3)

glob には言及がありませんでした。

Perl 同様、いくつかのページで「取得後に明示的にソートする必要がある」という内容はありましたが、保証しているかどうかは不明でした。

確認コードです。

import glob

for i in glob.glob("files/*"):
print(i)

# 出力:
#
# files/10.txt
# files/9.txt
# files/8.txt
# files/5.txt
# files/4.txt
# files/6.txt
# files/7.txt
# files/3.txt
# files/2.txt
# files/1.txt


PHP(7.1.7)

指定するフラグによって表示順の保証が変わります。

フラグ
保証されるか

GLOB_NOSORT
されない

指定なし
される

確認コードです。

<?php

foreach (glob("files/*") as $i) {
echo "$i\n";
}
?>

/**
* 出力:
*
* files/1.txt
* files/10.txt
* files/2.txt
* files/3.txt
* files/4.txt
* files/5.txt
* files/6.txt
* files/7.txt
* files/8.txt
* files/9.txt
*/

GLOB_NOSORT を指定したコードです。

<?php

foreach (glob("files/*", GLOB_NOSORT) as $i) {
echo "$i\n";
}
?>

/**
* 出力:
*
* files/10.txt
* files/9.txt
* files/8.txt
* files/5.txt
* files/4.txt
* files/6.txt
* files/7.txt
* files/3.txt
* files/2.txt
* files/1.txt
*/

参考


Go(1.9.2)

filepath - GoDoc には言及がありませんでした。

関連した話題のページはありましたが、Ruby/Python と同様不明でした。

package main

import (
"fmt"
"path/filepath"
)

func main() {
files, err := filepath.Glob("files/*")
if err != nil {
panic(err)
}
for _, file := range files {
fmt.Println(file)
}
}

/**
* 出力結果:
*
* files/1.txt
* files/10.txt
* files/2.txt
* files/3.txt
* files/4.txt
* files/5.txt
* files/6.txt
* files/7.txt
* files/8.txt
* files/9.txt
*/