相変わらず、騷しいう~さん
シクラメン

「旦那、nanoShell 完成したそうですね?」
『そやねん今回はええ勉強させてもろたで!』

「長いパス」対応の組み込みコマンド (nanoShell Tools)

1. 指定されたフォルダまたはファイルを複写する
 int copy( <folder[/wildcard]>, <folder> [," [sync] [progress]"])
 int copy( <file>, <file | folder> )
・名前 / サイズ / 日時 が異なるファイルのみ差分コピーする
・"sync" オプションを指定すると更に出力側にのみ存在するファイルを削除する(同期コピー)
   このメッセージは標準エラーに出力する (本体のメッセージは標準出力)

・ワイルドカード判定は、*、?、| を含む場合、
   または最後が ’.’ (ファイル名規約違反のため識別子として利用) の場合にワイルドカードと判断する
・ワイルドカードは、*→.*、?→.、.→\. に変換し、正規表現マッチする (大小文字を区別しない)
   こんな指定も可能です "[baz]*.txt|*.log"

・出力が (新規)フォルダの場合は、"foo/" と指定する

2. 指定されたフォルダまたはファイルを移動する
 int move( <folder[/wildcard]>, <folder>) [," [progress]"])
 int move( <file>, <file | folder> )

3. 指定されたフォルダまたはファイルを削除する
 int remove( <folder [/wildcard]> [," [root] [progress]"])
 int remove( <file>)
・ルートフォルダを指定する場合は "root" オプションが必要

4. 指定されたフォルダをコンパクトなツリー表示する
 int tree( <folder[/wildcard]> [," [file]"])
・"file" を指定するとファイル名を表示する

5. ファイルを一覧表示する (listSegment)
 int ls( <folder[/wildcard]> [," [path] | [0 | , | k] [260 | no260]"])
・ファイル長、0: 編集なし、, : カンマ編集、K: 単位変換 (属性を表示するためにはいずれかを指定する)
・path: ファイル長の代わりに パス長を sort キーとし使用するための編集("%, 7d")をして表示する
・260: 長いパスを表示、no260: 短いパス、省略: 全て

・ls はシェル連携をを考慮してタブ区切りで出力しています (日時、長さ、パス)

6. フォルダをクリーンアップする
 int clean( [," [clean] [noTime]"])
・"clean" オプションは空のフォルダを削除します
・"noTime"、標準ではフォルダ日時を子要素の日時に設定します(空フォルダは Time0)
・"move","remove"では入力側空フォルダ(Time0以外)を削除します

7. シェル変数を設定する
 int set( [," [var=val] …"])
・MAX_PATH=260 パスの最大長を指定する (tree、ls: 260、no260 オプションで使用)
・DISPLAY_WIDTH=40 パスを画面に表示する幅
・オプションを省略すると一覧表示します

8. 共通オプション
 プログレス: progress
・"copy", "move", "remove" で使用します
 ・リダイレクトと共存可能

 リダイレクト: [12]>{1,2} path
・リダイレクトは ①後続オプションが存在する場合は、ファイル名をシングルクオートで囲む仕様です >'path'
   これは後続の一般オプションを食ってしまうためで、②オプションの最後に指定または、
   ③リダイレクト達のみの単独指定でも回避できます
   すなわち "/*オプション*/ 1>foo 2>bar /*ここにオプションは書けない*/ "

・パスは、DISPLAY_WIDTH 桁に切り詰めますが、リダイレクトの場合はそのまま出力します

 リカーシブ: noRecursive
・オプションは大小文字を区別しないで前方一致で比較する (コマンド内でユニークなら良い)
   "foo", "bar" では煩雑になるため "foo bar" 形式を推奨する
   否定オプション noRecursive はマイナスオプション -r[ecursive] に短縮可能です

9. 復帰コード
 0: 正常、1: インフォーメーション、2: ワーニング(マゼンタ)、3: エラー(赤)
・重大エラーは、throw するためあまり意味を持た持たないため、警告に使用しています

10. 外部コマンド呼び出し
 int system("<command> [<args...>] [>redirect]")

11. PowerShell 呼び出し
 int system("PowerShell <command> [<args...>] [>redirect]")

12. コマンドプロンプト呼び出し
 int system("CMD /C <command> [<args...>] [>redirect]")

※ ソースコード: plus/io/NanoTools.java

CHANGELOG

  • 2022-12-28 sort コマンドを Android nanoShell から持って来た
  • 2022-12-28 CSV ツールにカラム削除(del)機能を追加した
  • 2023-01-01 ls コマンドに path オプションを追加した
  • 2023-01-05 set (シェル変数の設定)コマンドを追加した
  • 2023-01-09 コマンドラインのパスを切り詰めて表示するようにした
  • 2023-01-12 コマンドの実行時間を表示するようにした
  • 2023-01-19 clean コマンドを追加した
  • 2023-01-24 progress 機能を実装した
  • 2023-01-26 リファクタリング開始
  • 2023-01-28 async I/O を実装した
  • 2023-02-01 リファクタリング完了
  • 2023-02-04 clean コマンドの誤植を修正

振り分け

ls の出力をスクリプトで振り分けます

## nanoMove.awk -v MAX_PATH=260 redirect
#
var MAX_PATH = 260
BEGIN {
   print "\e[92m# ls コマンドの出力を moveコマンドで振り分ける\e[m"
   print "\e[92m# 準備\e[m"
   set("DISPLAY_WIDTH=40 MAX_PATH=" MAX_PATH)
   CURT = "./"
   INPUT = "W:/"
   LONG = "W:/Long/"
   MOVE = "W:/MOVE/"
   TEMP = "W:/TEMP/"
   remove(MOVE)
   copy(CURT "MATRIX*.html", TEMP, "-r")
   path50 = "---+----1----+----2----+----3----+----4----+----5"
   path100 = path50 "----+----6----+----7----+----8----+----9----+----0"
   path200 = path100 "B" path100
   fileName = "/Z" path200 path50 "_5.txt"
   path = LONG "A" path200 fileName
   print "foo" > path

   redirect = ARGV[1]
   print "\e[92m# リダイレクト\e[m"
   ls(INPUT, ">" redirect)
   tree(INPUT, "file")

   print "\e[92m# 実行開始\e[m"
}
   {
   if (length($0) > MAX_PATH) {  # ※1
       out = $0;
       sub("^.*?/.+?/", MOVE, out)  # ※2
       # sub("[^/]+$", "", out)     # ※3
       # copy($0, out)
       move($0, out)
       count += 1
   }
}
END {
   print "\e[92m# 確認\e[m"
   tree("W:/", "file")
   print "Total number of cases: " NR ", Number of processed: " count >"/dev/stderr"
   print "\e[92m# 完了\e[m"
}
※1 ls の「260」オプションを使用する手も有りますが、今回はスクリプトで振り分けました
※2 出力フォルダの設定、W:/IN/foo/file -> O:/OUT/foo/file
※3 (お好みで)ファイル名を削除、-> O:/OUT/foo/
※ sub は、javaなら replaceFirstです

※ make ファイル
nanoM :
 $(PLUS) sample/nanoMove.awk -v MAX_PATH=19 ./Tmove01.tmp

※ ls コマンドの出力
W:/Long/A---+----1----+----2----+----3…/Z---+----1----+----2----+----3.txt
W:/TEMP/lib.zip
W:/TEMP/lib2.zip
W:/TEMP/MATRIX1.html
W:/TEMP/MATRIX2.html
※ 出力結果は手動で切り詰めています

スクリーンショット

path1

・DISPLAY_WIDTHと
    MAX_PATH を設定

・前回のデータを削除してセットアップ

・ls の出力をリダイレクト

・マゼンタ(パス長 20以上)が移動対象です


path2

・move コマンドで移動します

・無事、"/MOVE" フォルダに移動しました


ベンチマーク (バックアップ)

ベンチマーク環境

  • ソース: ミュージックデータ、1,773楽曲、180フォルダ、65.5GB
    ".flac"(可逆圧縮)のためサイズが大きいため I/O効率が良い
  • 入力: 内蔵 SSD
  • 出力: 外付け(USB3.0) SSD

nanoShell

※ スクリプト
## nanoBackup.awk -v DISPLAY_WIDTH=n
#
var DISPLAY_WIDTH = 35
BEGIN {
   DD = "D:/"
   MM = "M:/"
   INPUT = DD "ミュージック/"
   OUTPUT = MM "ミュージック/"
   print "\e[92m# copy コマンドでバックアップする\e[m"
   set("DISPLAY_WIDTH="DISPLAY_WIDTH)
   print "\e[92m# 前回のバックアップを削除\e[m"
   remove(OUTPUT, "pro")
   print "\e[92m# バックアップ\e[m"
   copy(INPUT, OUTPUT, "pro")
}

※ make ファイル
nanoAC :
 $(PLUS) sample/nanoAsyncC.awk -v DISPLAY_WIDTH=35

スクリーンショット

path3

※ 結果は 4:09秒です
"copy", "move" のファイルI/O は "nio.Files" で実装しておりコードは 1step です
"Files" のバッファサイズが気になったためファイルを一気にI/Oしてみました

※ 結果は 8:19秒です
丁度、倍遅いです。ということは非同期 I/O(スレッド)を使っているということです

Robocopy (Windows標準)

※ バッチファイル
 robocopy.exe D:\ミュージック M:\ミュージック /E /MIR /DCOPY:DAT /R:1 /NP /NFL /NDL /LOG:.\robocopy.log

※ 結果は 3:53秒で、16秒差、射程距離内ですかね?

FastCopy

path4

※ 結果は 3:14秒(最速)です
設定画面を開くと小さなファイルはキャッシュする、非同期I/O=8 といった項目が並んでいます
Robocopy と比べて欠点はフォルダ・ファイルへの属性設定が限られていることです

中締め

『締めはこんなもんやろ?』
『リファクタリング前のベンチマークは鉄則やからな!何事もやらな判らんで』
「今後の展開が楽しみ~」

「敵を知り、己を知れば、百戦して殆(あや)うからず」(孫子)

世界の頂点を目指して

copy
  • 結果は copy(3:00秒 2:54秒) move(3:01秒)です (robocopy 3:53秒、FastCopy 3:14秒)
  • 出力側 Disk能力を100% 使い切っており、これ以上の性能向上は望めません
  • 舌の乾かないうちに 3分を切りましたが、あながち瞬間風速では無いようです ^^);
  • スレドプールAPIは、Executors.newWorkStealingPool を使用しており、
    すべての使用可能なプロセッサをターゲット並列性レベルとして使用します
    (DiskとCPUを限界まで使い切るAPIでう~さんの大好物です)
  • I/O処理完了待ちのためメインスレッドは遊んでいます
    (リクエストを一気に投げるため、完了までフォルダ構成は触れません)
  • copy/moveエラー発生時は 7回(wait 1sec.)リトライします
  • 謝辞、これはズルしておりSSD初期化後は数秒速い、おそらくTrimのせい ^^);
task

まとめ

プログラムコンテストで学んだことは、インスピレーション(閃き,お告げ,悟り)が全てだということです。
すなわち、潜在意識までダイブして答えを持ち帰ることです。
Top10 のアルゴリズムの差異は小さいですが、Topを狙うためにはアルゴリズムチューニングが必須です
コンテストの成果、国内 2/2千位、世界71/10万位がこれを証明しています。

『そや、ジョブズ君にもう一度登場してもらお』
「シンプルであることは、複雑であることよりもむずかしいときがある。物事をシンプルにするためには、懸命に努力して思考を明瞭にしなければならないからだ。だが、それだけの価値はある。なぜなら、ひとたびそこに到達できれば、山をも動かせるからだ。」

オープンソース

検証環境

  • Windows 10
  • Windows Terminal (オープンソース)、Windows PowerShell (オープンソース)
  • GNU Make (オープンソース)
  • Java 19 (オープンソース)
  • AWK˜plus for Java (オープンソース)

インストール

  1. Java をダウンロード(環境を汚さない .zip 版を推奨、複数の Javaもインストールできます)「Java Downloads」
  2. AWK~plus をダウンロード「AWK~plus for Java」 (コマンドを添付しています)
  3. AWK~plus フォルダ中の makefile の JAVAHOME 変数に Javaホームパスを設定する。

実行

ターミナルを開き、AWK~plus フォルダをカレントディレクトリにして、と入力する。

「Table of contents」 2023.02.01