概要
こんにちは、最近JShellに嵌っているuedaです。
この記事はワンライナー自慢大会 Advent Calendar 2018 - Qiitaの19日目です。
JavaのJShellを使ってテキストに書かれたとおりの構成のフォルダを出力する
ワンライナーを紹介します。
昔はJavaでワンライナーなんて考えもしませんでしたが、
Java9から使えるようになったJShell(REPL)で対話的に実行する事で、
ワンライナーっぽく書ける様になりました。
中々使う機会が無いので、勉強がてらワンライナーを作成しました。
環境
Java: openjdk version "11.0.1" 2018-10-16
OS: Windows10 Home
JShellを使ったJavaコマンド実行への道
Java11のダウンロード
JDKはいくつか種類がありますが、今回はAdoptOpenJDKを使いました。
adoptopenjdk.net
パスを通す
展開したAdoptOpenJDKのbinにパスを通し、「JShell」をコマンドプロンプトから実行できるようにします。
本題(フォルダ出力ワンライナー)
さて、本題のワンライナーについて。
大量のディレクトリを作んなきゃ、て事はたまにありますよね。
そんな時、さくっと生成したいと考え、
テキストに書いたとおりにフォルダを出力するワンライナーを書きました。
input.txt
Excelからぺっと貼り付けたタブインデント付のテキストです。
このテキストの構成どおりのフォルダ作成を考えます。
test test1 1-1 1-2 test2 1-1 1-2 2-1 3-1 images procedure 3-2 images procedure 3-3 images procedure
本稿の主役のワンライナー
上記input.txtと同じフォルダで下記ワンライナーを実行すると、テキストに記載された構成通りのフォルダが生成されます。
>echo Stream.of(new LinkedHashMap^^^<Integer, String^^^>()).forEach(map -^^^> {try { Files.readAllLines(Path.of("input.txt")).stream().peek(k -^^^> map.put(k.length() - k.stripLeading().length(), k.strip())).map(s -^^^> s.length() - s.stripLeading().length() == 0 ? s : String.join("/", map.values().stream().limit(s.length() - s.stripLeading().length()).collect(Collectors.toList())) + "/" + s.strip()).peek(System.out::println).forEach(r -^^^> {try {Files.createDirectory(Path.of(r));} catch (IOException e) {throw new UncheckedIOException(e);}});} catch (IOException e) {throw new UncheckedIOException(e);}}); | JShell -
解説
ワンライナーのままだと読みづらいので、展開してみます。
Stream.of(new LinkedHashMap<Integer, String>()).forEach(map -> { try { Files.readAllLines(Path.of("input.txt")).stream() .peek(k -> map.put(k.length() - k.stripLeading().length(), k.strip())) .map(s -> s.length() - s.stripLeading().length() == 0 ? s : String.join("/", map.values().stream().limit(s.length() - s.stripLeading().length()) .collect(Collectors.toList())) + "/" + s.strip()) .peek(System.out::println) .forEach(r -> { try { Files.createDirectory(Path.of(r)); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (IOException e) { throw new UncheckedIOException(e); } });
最初のStreamは、処理中で使用するLinkedHashMap生成のためだけに行います。
Stream.of(new LinkedHashMap<Integer, String>()).forEach(map -> 以降実際の出力処理
ファイルを読み込み、ファイル1行ごとの文字列に対しストリーム処理を行います。
Files.readAllLines(Path.of("input.txt")).stream()
peekはストリームに影響を与えない処理を行うため、ここでMapにファイル内容を格納しています。
Mapはキーがタブインデントの数、バリューがフォルダ名になるよう加工しています。
.peek(k -> map.put(k.length() - k.stripLeading().length(), k.strip()))
相対パスの生成を行っています。
タブ数が0ならそのまま、0以外なら全親フォルダのパス+自分のフォルダ名がストリームに流れます。
.map(s -> s.length() - s.stripLeading().length() == 0 ? s : String.join("/", map.values().stream().limit(s.length() - s.stripLeading().length()).collect(Collectors.toList())) + "/" + s.strip()).
生成された相対パスのDEBUG出力です。
.peek(System.out::println)
ちなみに
Streamを使わずに、展開して記述すると以下のようになります。
var map = new LinkedHashMap<Integer, String>(); var resultPathList = new ArrayList<String>(); List<String> lines = null; lines = Files.readAllLines(Path.of("input.txt")); for (String line : lines) { String pathName = ""; int tabCount = line.length() - line.stripLeading().length(); line = line.strip(); map.put(tabCount, line); if (tabCount == 0) { pathName = line; } else { for (int index = 0; index < tabCount; index++) { pathName += map.get(index) + "/"; } pathName += line; } resultPathList.add(pathName); } for (String resultPath : resultPathList) { System.out.println(resultPath); Files.createDirectory(Path.of(resultPath)); }
24Lineぐらいありますね。是に比べるとStreamを使ったコードはスッキリしています。
こちらのほうが読みやすいですが。。
まとめ
以上、JShellで書いたワンライナー、いかがでしたでしょうか。
Javaはあまり向いていないとは思いますが、
ストリームを書く練習にはちょうどよいのではないかと思います。
ではでは。
Acroquest Technologyでは、キャリア採用を行っています。
- ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
- Elasticsearch等を使ったデータ収集/分析/可視化
- マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
- 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。世界初のElastic認定エンジニアと一緒に働きたい人Wanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップの求人 - Wantedlywww.wantedly.com