Ruby
Python
Perl
PHP
Go

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

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
 */