BashScript を Elixir で書き直してみたっ（２倍速〜）

---

(この記事は[「fukuoka.ex Elixir／Phoenix Advent Calendar 2018」](https://qiita.com/advent-calendar/2018/fukuokaex)の15日目です)

昨日は@zacky1972さんの
[ZEAM開発ログ2018年総集編その2: Elixir 研究構想についてふりかえる(後編) ](https://qiita.com/zacky1972/items/a754a769ac7923edb79c)
でした！

---

この記事は[「古い写真のアルバムをスマホで見るようにしたお話」](https://qiita.com/callmekohei/items/beba68200239c895d098)の後日談です

---

<br>
<br>


![IMG_8367.jpeg](https://qiita-image-store.s3.amazonaws.com/0/149269/55696a48-2a9b-8db2-5fab-ae21aab521f6.jpeg)


## Summary

`Exif`データーを編集する`BashScript`のコードを`Elixir`で書いてみました
実行時間が約半分になったよ

## 実行時間の比較

Elixir

```bash
$ time elixir foo.exs

real    5m48.747s
user    0m2.563s
sys     0m1.753s
```

BashScript

```bash
$ time bash foo.bash

real    9m2.675s
user    7m19.833s
sys     1m14.571s
```

## Motivation


[「古い写真のアルバムをスマホで見るようにしたお話」](https://qiita.com/callmekohei/items/beba68200239c895d098)で写真を整理するために`BashScript`を書いたのですが結構遅かったので、もう少し早くならないかな〜と思ったので、、、


## ってElixirってなに？

なんでしょう？ `callmekohei`もよくは`Elixir`を知りません（すいませんっ）

今時点`callmekohei`が`Elixir`について知っていることは

1. スクリプト言語だよ（手軽に書けるよ）
2. 左から右にるるるる〜という言語だよ（`|>`があるよ）
3. `F#`という言語にすごく似てるよ

ぐらいですっ

## ってなんでElixir？

しょっちゅう`Twitter`のタイムラインに流れてくるからです

@piacere_ex さんというかたをフォローしたら怒涛のごとく`Elixir`の情報が流れてきました（笑）

`もくもく会`というのがあって先日参加させていただいたら`Elixir`に興味が出てきました！

fukuoka ex のもくもく会はここを覗いてみてください！
[---> fukuokaex もくもく会](https://fukuokaex.connpass.com/)


## Elixir で Exif データーを編集してみる

[F#を横目にElixirで「こんにちは世界」](https://qiita.com/callmekohei/items/411707d9158f804f98a4) で`Elixir`の「こんにちは世界」ができたのでさっそく実際にコード書いてみました！

[古い写真のアルバムをスマホで見るようにしたお話 05-05 BashScriptで連番振りスクリプト書いてみた](https://qiita.com/callmekohei/items/beba68200239c895d098#05-05-bashscript%E3%81%A7%E9%80%A3%E7%95%AA%E6%8C%AF%E3%82%8A%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%BF%E3%81%9F) で書いた`BashScript`のコードを`Elixir`で書き直してみました！（もっといい方法があるかもしれません・・・・）

```elixir
defmodule Foo do

  def sortedFileNameList( fp ) do

    exifToolParam = [
      "-quiet",
      "-fast2",
      "-s3",
      "-filename",
      "-fileorder datetimeoriginal",
      "-fileorder filename"
    ]

    bashCommand = [
      "-c",
      "cd " <> fp <> " ; echo *jpg | xargs exiftool " <> Enum.join( exifToolParam , " " )
    ]

    System.cmd("bash" , bashCommand )
    |> elem(0)
    |> ( fn x -> String.split(x ,"\n") end ).()
    |> Enum.drop( -1 )

  end

  def resetAllTime( fp ) do

    exifToolParam = [
      "-quiet",
      "-fast2",
      "-s3",
      "-d '%Y:%m:%d 00:00:00'",
      "-overwrite_original_in_place",
      "\"-alldates<alldates\""
    ]

    bashCommand = [
      "-c",
      "cd " <> fp <> " ; echo *jpg | xargs exiftool " <> Enum.join( exifToolParam , " " )
    ]

    System.cmd("bash" , bashCommand )
    |> elem(0)

  end

  def addOneSecond( fp ) do

    exifToolParam = [
      "cd " <> fp <> " ; exiftool",
      "-quiet",
      "-fast2",
      "-s3",
      "-overwrite_original_in_place -alldates+=0:0:"
    ]

    exifToolParam2 =
      Foo.sortedFileNameList( fp )
      |> Enum.with_index()
      |> Enum.map( fn { filename , idx } ->
        Enum.join( exifToolParam, " ") <> Kernel.inspect(idx) <> " " <> filename
      end )

    exifToolParam2
    |> Enum.each( &( System.cmd("bash",["-c" , &1]) ))

  end

  # 確認用
  def myCheck( fp ) do

    exifToolParam = [
      "-quiet",
      "-fast2",
      "-s3",
      "-filename",
      "-datetimeoriginal",
      "-fileorder datetimeoriginal",
      "-fileorder filename"
    ]

    bashCommand = [
      "-c",
      "cd " <> fp <> " ; echo *jpg | xargs exiftool " <> Enum.join( exifToolParam , " " )
    ]

    System.cmd("bash" , bashCommand )
    |> elem(0)
    |> ( fn x -> String.split(x ,"\n") end ).()
    |> ( fn lst -> lst -- [""] end ).()

  end

  def jpgFolderList(fp) do
      Path.wildcard( Path.expand( fp ) <> "/**/*.jpg" )
      |> Enum.map( &( Path.dirname( &1 )))
      |> Enum.uniq
  end

  def mainImpl( foo ) do

    " sortedFileNameList " |> IO.puts
    Foo.sortedFileNameList(foo)

    " resetAllTime " |> IO.puts
    Foo.resetAllTime(foo)

    " addOneSecond " |> IO.puts
    Foo.addOneSecond(foo)

    " myCheck " |> IO.puts
    Foo.myCheck(foo) |> Enum.each( &( IO.puts( &1 )))

  end

  # 写真データーのExifデーターを変更できるようにするために権限を変える
  def chmod644(fld) do
    System.cmd("bash" , ["-c", "cd " <> fld <> " ;  chmod 644 *jpg"] )
  end

end

defmodule Main do

  # 写真データーがあるフォルダを指定する
  baseFolder = "/Users/callmekohei/Desktop/tmptmp"

  # すべての写真データーのpermissionを644にする
  Foo.jpgFolderList( baseFolder )
  |> Task.async_stream( &( Foo.chmod644( &1 ) ) ,[ timeout: :infinity, max_concurrency: 1000] )
  |> Enum.to_list()

  # メイン部分を実行
  Foo.jpgFolderList( baseFolder )
  |> Task.async_stream( &( Foo.mainImpl( &1 ) ) ,[ timeout: :infinity, max_concurrency: 1000] )
  |> Enum.to_list()

end
```

まえに書いた`BashScript`より早くなってますね。パラレルが効いてるのでしょうか？

## はまったところ

下記のように`Task.async_stream`を２重に使うと扱うデーター数が多くなるとタイムアウトでプログラムが止まってしまいます。今回の場合フォルダ数が５個以上の時にエラーが頻発しました


```elixir

  def addOneSecond( fp ) do
    exifToolParam2
    |> Task.async_stream( &( System.cmd("bash",["-c", &1]) ))
    |> Enum.to_list()
  end


  Foo.jpgFolderList( baseFolder )
  |> Task.async_stream( &( Foo.mainImpl( &1 ) ) ,[ timeout: :infinity, max_concurrency: 1000] )
  |> Enum.to_list()
```

こんな感じのエラーがでます

![15.png](https://qiita-image-store.s3.amazonaws.com/0/149269/90f19641-15a7-53ad-258c-aa9fd5bf7c27.png)

（エラー回避）
内側の部分のアシンクを外します

```elixir
  def addOneSecond( fp ) do
    exifToolParam2
    |> Enum.each( &( System.cmd("bash",["-c" , &1]) ))
  end
```

## 感想

ほむほむ

`Elixir`

いい感じ

## 冒頭のイラスト

ざっきー先生（山崎 進先生 @zacky1972さん ）という、ヘイスガという`GPU`でエリクサーの演算をするライブラリ？を開発されているすごい方です！この前もくもく会でお見かけした時はずっとチョコフレーク？をもぐもぐされてましたよ。