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 には言及がありませんでした。
「取得後に明示的にソートする必要がある」というページを見つけましたが、順番を保証しているかどうかは不明でした。
- Ruby で Dir.glob して require するときは sort してからにする | Born Too Late
- ruby - Does Dir.glob guarantee the order? - Stack Overflow
確認コードです。
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_CSH
はGLOB_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 と同様不明でした。
- path/filepath: ensure Glob() returns in lexical order and comment about that · Issue #17153 · golang/go
- [go-nuts] Order of files returned by filepath.Glob - Grokbase
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
*/