selmertsxの素振り日記

ひたすら日々の素振り内容を書き続けるだけの日記

mongoDBでテーブル作成、アイテム作成、アイテム取得をやってみる

モチベーション

GitHub - selmertsx/serverless-prpolice

自分が作っているサービスでmongoDBを触ることになったので、下記の項目を理解したい。

  • mongoDBの概要
  • mongoDBにおける基本的な操作の把握
    • Tableの作成
    • Itemの作成
    • Itemの取得

mongoDBの概要

  • Table、Item、Attributesの3つで出来てる
  • TableはItemの集合
  • ItemはAttributesの集合

DynamoDBはプライマリキーを使用して、テーブルのItemを一意に識別する。Itemの一部には、入れ子のAttributesを作ることができる。

{
    "PersonID": 1, #こいつがプライマリキー
    "LastName": "Sample",
    "Address": {
        "Street": "123 Hoge"
    }
}

パーティションキー

一つの属性で構成されたシンプルなプライマリキー。パーティションキーの値を内部ハッシュ関数への入力として利用する。これによって、Itemを一意に識別できるようにする。

複合プライマリキー

最初の属性であるパーティションキーと2番めの属性のソートキーによって構成される。同じパーティションキーを持つ全てのItemは、ソートキー値でソートされて保存される。同じパーティションキーのItemは複数存在しても、同じパーティションキー & ソートキーのItemが複数存在することは許されない。

mongoDBにおける基本的な操作

Tableの作成

aws dynamodb create-table \
  --table-name User \
  --attribute-definitions \
    AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
  --endpoint-url http://localhost:8000
  • Attributesとして定義したものには KeySchemaを設定しなければならない
  • 複合プライマリキーの場合、二番めキーのKeyTypeはRANGEとなる
  • Attributesとして定義しなくとも、値を格納することはできる

Itemの作成

import AWS from "aws-sdk";

const dynamo = new AWS.DynamoDB({
  endpoint: "http://localhost:8000",
  region: "ap-north-east1"
});

const putParams = {
  TableName: "User",
  Item: { Id: { S: "SlackID" }, github: { S: "GitHubID" } }
};

dynamo.putItem(putParams, (err, data) => {
  console.log(err);
  console.log(data);
});

Itemの取得

import AWS from "aws-sdk";

const dynamo = new AWS.DynamoDB({
  endpoint: "http://localhost:8000",
  region: "ap-north-east1"
});

const getParams = {
  Key: {
    Id: {
      S: "SlackID"
    }
  },
  TableName: "User"
};

dynamo.getItem(getParams, (err, data) => {
  console.log(err);
  console.log(data);
});

その他リンク集

期限切れになった項目を自動的にテーブルから削除することも可能 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/TTL.html

ベストプラクティス集 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/BestPractices.html

ふつうのLinuxプログラミング(4章)

学べること

ディレクトリのパーミッションの挙動は下記の通り。

  • read => ファイル一覧が取れる
  • write => ディレクトリの中にファイルを作れる
  • execute => そもそもファイルにアクセスできない

僕達がコマンドラインとして認識しているのは、shellとterminalという2つの構成要素から成る。端末はファイルとして表現される。

※ 端末については、こいつをファイルと見立てて入出力のストリームを作成することで、コンピューターと人間がやりとりできるようにするもの。というくらいの認識でいる。

以下実験内容

ファイルのパーミッション

普通につくると、パーミッションは実行権限を持たない

$ echo "echo 'hello'" > sample.sh
$ ls -l
total 8
-rw-r--r--  1 shuhei_morioka  admin  13  2 14 21:42 sample.sh
# shell command が sample.sh を読み込んで実行している
$ sh sample.sh
hello
# パイプを使ってストリームを作成し、sh コマンドに渡して実行することも可能
$ cat sample.sh | sh
hello
# 実行権限が無いので実行不可
$ ./sample.sh
zsh: permission denied: ./sample.sh
# sample.sh から読み込み権限を抜くと、shell scriptで実行不可
$ chmod -r sample.sh
$ sh sample.sh
sh: sample.sh: Permission denied
# 実行権限を付与すると、./sample.sh 単体で実行可能
$ chmod +x sample.sh
$ ls -l
total 8
-rwxr-xr-x  1 shuhei_morioka  admin  13  2 14 21:42 sample.sh
$ ./sample.sh
hello

ディレクトリのパーミション

読み込み権限が無いディレクトリの場合

$ mkdir sample_dir
$ ls -l
total 8
-rwxr-xr-x  1 shuhei_morioka  admin  13  2 14 21:42 sample.sh
drwxr-xr-x  2 shuhei_morioka  admin  68  2 14 22:33 sample_dir
$ chmod -x sample_dir
$ ls -l
total 8
-rwxr-xr-x  1 shuhei_morioka  admin  13  2 14 21:42 sample.sh
drw-r--r--  2 shuhei_morioka  admin  68  2 14 22:33 sample_dir
$ cd sample_dir
cd: permission denied: sample_dir

実行権限が無いディレクトリの挙動

➜  permission chmod +x sample_dir
➜  permission touch sample_dir/hoge
➜  permission ls -l sample_dir
total 0
-rw-r--r--  1 shuhei_morioka  admin  0  2 14 22:35 hoge
➜  permission chmod -x sample_dir
➜  permission ls -l
total 8
-rwxr-xr-x  1 shuhei_morioka  admin   13  2 14 21:42 sample.sh
drw-r--r--  3 shuhei_morioka  admin  102  2 14 22:35 sample_dir
➜  permission ls -l sample_dir
ls: hoge: Permission denied

端末の実験

今の端末のファイルを確認する方法

➜  permission tty
/dev/ttys000

別の端末から、/dev/ttys000 に文字列を入力すると

➜  ~ tty
/dev/ttys003
➜  ~ echo "hogehoge\nhogehoge" > /dev/ttys000

/dev/ttys000 の端末に出力される。

# /dev/ttys000 の端末
➜  permission hogehoge
hogehoge

TypeScript 2.7での esModuleInteropをつけたコンパイルの挙動

モチベーション

GitHub - selmertsx/serverless-prpolice

個人プロダクトの開発中に、TypeScriptのコードが buildすると実行できるのに、Jestのテストだけコケるという問題が発生した。その原因を調査したかった。

前提条件

"typescript": "^2.7.1"

問題の内容

consoleの内容

# 呼び出し元のコード
import { GitHubApi } from "@octokit/rest";
const github = new GitHubApi();
github.authenticate({...})
$ yarn test
yarn run v1.3.2
$ jest
 PASS  __tests__/github.spec.ts
  pullRequests
    ✓ get (850ms)
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.1s, estimated 6s
$ bin/prpolice local get_pr
(node:1) UnhandledPromiseRejectionWarning: 
Unhandled promise rejection (rejection id: 1): TypeError: rest_1.default is not a constructor

調査

# buildされたコード
Object.defineProperty(exports, "__esModule", { value: true });
var rest_1 = require("@octokit/rest");
var pull_request_1 = require("./pull_request");
var GitHub = (function () {
    function GitHub(owner, repo) {
        this.github = new rest_1.default();

https://github.com/octokit/rest.js/blob/master/index.js

module.exports = GitHubApi
function GitHubApi (options) {
  ....
end

Compiler Options · TypeScript

Emit importStar and importDefault helpers for runtime babel ecosystem compatibility and enable --allowSyntheticDefaultImports for typesystem compatibility.

# --esModuleInterop をつけてコンパイルされたコード
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
}
Object.defineProperty(exports, "__esModule", { value: true });
var rest_1 = __importDefault(require("@octokit/rest"));
var pull_request_1 = require("./pull_request");
var GitHub = (function () {
    function GitHub(owner, repo) {
        this.github = new rest_1.default();
        this.owner = owner;
        this.repo = repo;
    }
# コンパイルされたコードの挙動の確認
$ node
> const a = { "default": require("@octokit/rest") }
undefined
> a.default
[Function: GitHubApi]
> a.default()
{ hook:
   { [Function: bound register]

このコードで、jestも sam localのテストも動くことが確認できました。

20180208の学び

最初に

これは個人プロダクトで学んだ内容をまとめたもの。

GitHub - selmertsx/serverless-prpolice

忘れる前に書いておくけれども、後でちゃんと整理する

学んだ内容

AWS

  • cloudformation でlambdaをdeployする前に、awsのs3にbucketを作っておかなければならない
aws s3api create-bucket --bucket prpolice --create-bucket-configuration LocationConstraint=ap-northeast-1
# まずはAPI Gatewayの Method Request レイヤーで受け取る
HTTP Method: POST, Resource Path: /index
Method request path: {}
Method request query string: {}
Method request headers: {Accept=*/*, CloudFront-Viewer-Country=US, CloudFront-Forwarded-Proto=https, CloudFront-Is-Tablet-Viewer=false, CloudFront-Is-Mobile-Viewer=false, User-Agent=Slackbot 1.0 (+https://api.slack.com/robots), X-Forwarded-Proto=https, CloudFront-Is-SmartTV-Viewer=false, Host=xxx, Accept-Encoding=gzip,deflate, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=xxx, Via=1.1 xxx.cloudfront.net (CloudFront), X-Amz-Cf-Id=xx==, X-Forwarded-For=xxxx, CloudFront-Is-Desktop-Viewer=true, Content-Type=application/json}
Method request body before transformations:
{
    "token": "xxxx",
    "challenge": "xxx",
    "type": "url_verification"
}

# その後、Integration Requestにリクエストを流す
Endpoint request URI: https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:xxx:function:prpolice-xxx/invocations
Endpoint request headers: {x-amzn-lambda-integration-tag=xxx, Authorization=***, X-Amz-Date=20180208T122332Z, x-amzn-apigateway-api-id=b3kfmkg7ig, X-Amz-Source-Arn=xxx, Accept=application/json, User-Agent=xxx, X-Amz-Security-Token=xxx [TRUNCATED]

Slack

20180209にやること

  • とりあえず動くところまで持ってく
  • API Gatewayを含めた動作をいい感じにテストできるようにしたい
    • AWS SAM Local上でテストできるようにしたい

一からtslintの設定をする方法

モチベーション

tslintについては、これまで誰かが作ってくれた設定ファイルを使ってきました。 しかし、そろそろちゃんと理解したいと思い、諸々調べてみました。 この資料に tslint を一から設定する方法を記載し、自分の理解を整理しようとおもいます。

※ 理解が間違っている点ありましたら、コメント貰えたら嬉しいです!

そもそもLint って何?

lint (software) - Wikipedia

そもそもLintについて理解がふわっとしていたので、wikipediaを読みました。linterとはソースコードを解析してプログラムの保守性・安全性を保ち、機能的なバグを防ぐためのもの。特にJavaScriptPythonなどのコンパイルフェーズを持たないものにとっては、実行する前にバグを見つけられることができるので有用とのことでした。

tslint 導入手順

  • install
  • configuration presetsの導入
  • 適用するrulesの選択

tslint install

TSLint command-line interface

上記ドキュメントを見て、tslintを導入していきます。 下記のコマンドをポチッと実行しましょう。

yarn add -D tslint

Exit Codesは、ルールを詳細に設定する際に必要となるため、ここにあるということだけは覚えておくと良いと思います。

Exit Codes
0: success
1: cliの引数エラー
2: Linting failed

configuration presets の導入

次は、下記のlinkからLintの設定方法について確認していきます。 Configuring TSLint

色々書いてありますが、最初は extendsConfiguration presets だけ見ておけば良いでしょう。extendsには下記のような説明が記載されています。

The name of a built-in configuration preset (see built-in presets below), or a path or array of paths to other configuration files which are extended by this configuration.

extendsは、build-inのプリセット設定ファイルを入れたり、設定ファイルのパスを記載するプロパティです。ここに記載するべき設定ファイルは、Configuration presets にて説明されています。Configuration presetsには、下記3つの設定ファイルが存在します。

presets description
tslint:recommended stable な設定ファイル
tslint:latest tslint:recommendedと新しく追加されたルールを含む
tslint:all 全てのチェックを行う設定ファイル

さて、ミニマムでtslintを使ってみます。 最もスタンダードそうな tslint:recommendedextends に記載します。 このとき設定ファイルの名前は tslint.json とする必要があります。

// tslint.json
{
  "extends": ["tslint:recommended"]
}

この設定でlintを実行していきましょう。下記のような結果が返ってきます。

$ yarn tslint 'src/**/*.ts'

ERROR: /xxx/iiopt/src/cli.ts[22, 1]: Exceeds maximum line length of 120
ERROR: /xxx/iiopt/src/cli.ts[29, 9]: The key 'alias' is not sorted alphabetically
ERROR: /xxx/iiopt/src/cli.ts[30, 23]: Missing trailing comma

vscode上で確認してみると、lintでErrorとなっている場所が、赤くなっていることが確認できます。

f:id:selmertsx:20180206192918p:plain

ルールの修正

Rule: object-literal-sort-keys

# bad
      overwrite: {
        type: "boolean",
        alias: "o",
        default: false,
      },
# good
      overwrite: {
        alias: "o",
        default: false,
        type: "boolean",
      },

ということですね。理由としては、下記のように記載されています。

Useful in preventing merge conflicts

overwrite objectにおいて、typeは渡されるデータの型、default は値が渡されなかった際のデフォルト値です。なのでこのルールに従うと、typeの前に defaultを渡す必要が出てきます。僕としては、まず型の情報を確認し、その後にデフォルト値を渡した方がしっくりくるので、それを阻むこのルールは取り除くことにしました。そのtslint.jsonの設定は下記のようになります

{
  "extends": ["tslint:recommended"],
  "rules": {
    "object-literal-sort-keys" : false
  }
}

vscode上でも怒られなくなりました。

f:id:selmertsx:20180206201656p:plain

まとめ

tslintの設定は一気にやろうとすると混乱するので、特に理由がない限り最初は configuration presetsを使って、ERRORになった部分を一つずつ確認し、ルールを作っていくとよいのかなぁと思いました。

補足: 修正しなかったルールとその理由

trailing comma

$ yarn tslint 'src/**/*.ts'
ERROR: /xxx/iiopt/src/cli.ts[30, 23]: Missing trailing comma <= このエラー

trailing commaに関しては、下記のドキュメントに記載されています。 Rule: trailing-comma

# NG
      overwrite: {
        type: "boolean",
        alias: "o",
        default: false
      },
      overwrite: {
        type: "boolean",
        alias: "o",
        default: false,
      },

この理由について調査してみると、airbnbの資料にそれっぽい説明がありました。

GitHub - airbnb/javascript: JavaScript Style Guide

Why? This leads to cleaner git diffs. Also, transpilers like Babel will remove the additional trailing comma in the transpiled code which means you don’t have to worry about the trailing comma problem in legacy browsers.

git diffsがなくなるから良い。legacy browsersでエラーになることがあったけど、Babelを使えば問題無いよね。とのこと。納得したので、このルールは適用しようと思います。

ふつうのLinuxプログラミング読書 (3章)

ファイルとは

  • 何らかのデータを保持し
    • 何らかのデータとは、テキスト、画像、
  • パーミッションや更新時間などの付帯情報を保持しており
  • 名前で指定して閲覧・実行ができるものである

ファイルの種類

  • Regular File
    • 画像。動画。テキストなど。
  • Directory
    • このファイルの中に、入っているファイル一覧
  • Symbolic Link
    • このファイルが指し示す一つのファイル
  • Device File
    • バイスをファイルとして表現したもの
    • マウス、キーボード、モデム、HDD、SSDなどが含まれる

シンボリックリンクとハードリンク

本には書かれていないので、自分で確認したことを記載。

$ echo 'hogehoge' > ln.txt
$ ln -s ln.txt ln_soft.txt
$ ln ln.txt ln_hard.txt
$ ls -li
total 32
56944157 -rw-r--r--  2 shuhei_morioka  admin   9  2  5 20:49 ln.txt
56944157 -rw-r--r--  2 shuhei_morioka  admin   9  2  5 20:49 ln_hard.txt
56944166 lrwxr-xr-x  1 shuhei_morioka  admin   6  2  5 20:49 ln_soft.txt -> ln.txt

このような形でハードリンクとソフトリンクを使って、ファイルを生成しました。

$ cat ln_soft.txt
hogehoge
$ cat ln_soft.txt
cat: ln_soft.txt: No such file or directory
$ cat ln_hard.txt
hogehoge

ソフトリンクは参照しているファイルを削除してしまうと、何のファイルにもアクセスできなくなってしまいます。しかし、ハードリンクでは直接アクセスするためのi-nodeをリンク元のファイルと共有しているため、リンク元のファイルが削除されてもアクセスすることが出来ます。

ソフトリンクでは、リンク先のパスのみを保持しているため、新しいファイルを元のパスに配置すると読み込んでくれるようになります。

$ readlink ln_soft.txt
ln.txt
$ echo 'fugafuga' > ln.txt
$ cat ln_soft.txt
fugafuga

このようにソフトリンクの方が、プログラムから扱いやすそうに見えます。

Linux: ハードリンクと inode - Qiita シンボリックリンクとハードリンクの違い - Qiita

ファイルシステム

物理的なディスクをパーティションとして区切り、その上にファイルシステムをマウントさせることで、ファイルを扱う土台となるディレクトリツリーが出来上がります。以下、macosamazon linuxファイルシステムが違うよということの確認していきます。

# macosで実行した結果
➜  ~ mount
/dev/disk1 on / (hfs, local, journaled)
devfs on /dev (devfs, local, nobrowse)
map -hosts on /net (autofs, nosuid, automounted, nobrowse)
map auto_home on /home (autofs, automounted, nobrowse)
# amazon linuxで実行した結果
ec2-user@bastion ~
$ mount -t ext4
/dev/xvda1 on / type ext4 (rw,noatime,data=ordered)

プロセスとは

動作中のプログラムのこと。helloというプログラムを動かすと、helloプロセスが生成される。 プログラムとプロセスは1対多の関係であり、プロセスのユニーク性の識別するには Process ID を用いる。プロセスへ命令するものは シグナル と呼ばれ、よく使われるものとして Ctrl + c の割り込みシグナルなどがある。

ストリームとは

バイトが流れる通り道。つまり byte streamのこと。本書独自の用語である。 プロセスからファイルにアクセスするとき、プロセスはシステムコールを使って、ファイルやプロセスにアクセスするためのストリームを作る。

ファイルにアクセスができるので、テキストや画像だけでなく、ファイルの一種であるデバイスファイル(キャラクタデバイスであるモデムや、ブロックデバイスである HDD/SSDなど)にもアクセスすることができる。

プロセスとプロセスの間でストリームを通じて行われるデータのやりとりをプロセス間通信と呼ぶ。プロセス間通信の中には、下記の2つのやりとりが存在する。

  • パイプ
    • ストリームの向き先に別のプロセスを用意すること
  • ネットワーク通信
    • ストリームを別のコンピューターまで伸ばすこと

まとめ

  • 画像・テキストが格納されていたり、プログラムが記載されていたりする ファイル
  • プログラムを実際に実行している プロセス
  • プロセスが実行した結果をディスプレイ、ファイルなどに出力するためのデータの流れを作り出す ストリーム

この3点がLinux世界を構築する上で重要な概念である。

JavaScriptで正規表現(入門レベル)

モチベーション

現在、iioptという画像圧縮ツールを作成しています。このツールの一つの機能として、git pre-commit にhookしてcommit対象の画像を抽出し、圧縮が必要であれば圧縮するという機能を開発中です。

本記事は、そこで学んだ正規表現の書き方について記載します。記事の作成にあたって、下記のサイトを参考にさせていただきました。

developer.mozilla.org

※ 今まであんまり正規表現に触ってこなかったゆるふわエンジニアの記事なので、ある程度触った方には学ぶことがないかと思います。そのような方は、回れ右して頂くか、間違ってる点とう発見したらコメント貰えると喜びます!

やりたいこと

git pre-commit 時に git diff --cached --name-status を実行したとき、下記の結果が得られたとします。

# git diff --cached --name-status
M       images/need_match.jpg
M       images/not_matchjpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png

このとき、頭文字がM,Aであり、かつ拡張子がpng, jpgのファイル名のみを取得したいです。なので、求める最終的なアウトプットは下記のようになります。

'images/need_match.jpg',
'images/need_match.png',
'need_match.jpg',
'need_match.png'

最終的に採用したコード

  const input = `
M       images/need_match.jpg
M       images/not_matchjpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png
`
  const re = /^[AM]\s.+\.(?:jpg|png)$/gm;
  const files = input.match(re).map((line) => {
    console.log(line);
    return line.replace(/^[AM]\s*/, "")
  });
  console.log(files);
'images/need_match.jpg',
'images/need_match.png',
'need_match.jpg',
'need_match.png'

パターンについて

頭文字の判断

まず、頭文字 A & Mのもののみ取得したいです。 その場合、正規表現/^[AM]/となります。 実際にコードを書いて確かめると、下記のような結果が得られます。

console.log(/^[AM]/.test('A       sample1.jpg'));
console.log(/^[AM]/.test('D       hoge.jpg'));
true
false

ここで把握するべきものは、^[]の2つです。

まず[] についてです。これは文字列の集合を意味し、囲まれた文字のいずれか 1 個にマッチします。[]の中では - を使えば範囲を指定することができます。[0-9]とすれば、0から9までの数値すべてにマッチします。

> /[A-Z]/.test('BC123')
true
> /[A-B]/.test('123')
false

次の^ は、入力の先頭にマッチします。例えば、'BA' という文字列があったときに、/A/はマッチしますが、/^A/ はマッチしません。

ということで /^[AM]/ という正規表現を用意すれば、頭文字 A or M の文字列にマッチさせることができます。

特殊文字\sでタブ or 空白にマッチさせる

頭文字 A or M にマッチさせることができたので、次は空白 or タブにマッチさせたい。ここで把握するべき要素は、\s である。

まず、'M hoge.jpg' にマッチする正規表現を書きたい。 このとき必要となるのが、特殊文字 \s であす。 \sはmdnのドキュメントにおいて、「スペース、タブ、改ページ、改行を含む 1 個のホワイトスペース文字にマッチ」するものと書かれています。

> /^[AM]\s/.test('Mhoge')
false
> /^[AM]\s/.test('M hoge')
true

末尾マッチとグループ化

これまでの内容で出来上がった正規表現は、 /^[AM]\s/となります。この正規表現からマッチする文字列は、 A hogehogeM hogehoge のような文字列です。

ここから、A hoge_hage/hogehoge.jpgM hogehage.png のような文字列にマッチさせる正規表現になるよう、仕上げていく必要があります。今回は ., +, と$、そしてグループ化です。

まず . ですが、これは改行文字以外のすべての文字列にヒットします。次に+は、直前の文字の1回以上の繰り返しにマッチします。よって、.+ は1文字以上何かの文字列があれば、全てにマッチさせることになります。

> /^[AM]\s.+/.test('A &&&hogeあ')
true

このように自由度高く受け入れられるようにした理由としては、ユーザーがどのような名前の画像を用意するかわからなかったため、受け入れられる文字列を幅広く設定したかったからです。iioptはCLIツールなので、自分で自分に悪意を持って操作しない限り、問題が起きる可能性が少ないと考えたためです。

次にjpg, pngの拡張子を持つパスを取得できるようにします。ここで重要になってくるのが、$です。まず、jpgの拡張子を持つファイルだけ取得できるようにしましょう。$入力の末尾にマッチする特殊文字なので、/.jpg$/ のように書くと、hogehoge.jpg などのファイルパスにマッチできるようになります。これまで正規表現とくっつけると、下記のようになります。

> /^[AM]\s.+\.jpg$/.test('A &&&/hoge')
false
> /^[AM]\s.+\.jpg$/.test('A &&&/hoge.jpg')
true
> /^[AM]\s.+\.jpg$/.test('A &&&/hoge.jpga')
false

jpgだけでなくて、pngも取得したいです。ここでグループ化 という概念が出てきます。

正規表現 - JavaScript | MDN

グループ化は、(?: PATTERN) といった書き方で使います。簡単な例を以下に記載します。

 // "foo"で1つのパターンとして認識するため、"foofoo"がtrueになる
> /(?:foo){2,3}/.test("foofoo")
true
// "o" がパターンとして認識される。そのため、foofoo だとoが3つ続かないのでfalse
> /foo{2,3}/.test("foofoo")  
false
// "foooo" はfoの後に o が2~3続くのでtrue
> /foo{2,3}/.test("foooo") 
true

このグループ化を使うと、jpg, png の両方の拡張子に対応する正規表現は、/\.(?:jpg|png)$/ となります。 これまでの正規表現と繋げると、/^[AM]\s.+\.(?:jpg|png)$/ となって、ここのサイトを使って図にすると、下記の図のようになります。

f:id:selmertsx:20180130223827p:plain

※ この正規表現に、セキュリティ的に問題がある!などご存知の方がいましたら、ご指摘頂けると幸いです。

正規表現フラグ

/m フラグ

これまでの結果から、M need_match.png のような文字列が与えられたときに、マッチするために必要な正規表現/^[AM]\s.+\.(?:jpg|png)$/ であるということがわかりました。でも今回与えられる文字列は複数行あります。そういうときは、 /m フラグをつけましょう。複数検索ができるようになります。/mをつけて、確認した結果を以下に記載します。

  const input = `
M       images/not_matchjpg
M       images/need_match.jpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png
`
  const re = /^[AM]\s.+\.(?:jpg|png)$/m;
  const matches = input.match(re);
  console.log(matches);
[ 'M       images/need_match.jpg',
  index: 1,
  input: '\nM       images/need_match.jpg\nM ...

2行目の取得するべき文字列を取得してくれました。 が、3行目以降は検索しませんでした。 以上の結果から、/m だけでは、全ての行から一致するものを取得することができません。

/mg フラグ

そこで必要となってくるのが、/g フラグです。 これは、グローバルサーチ と呼ばれるもので、マッチした全ての文字列を取得することができます。 下記が実行結果になります。

// /g をつけないと最初に一致したもので終了
> `hage hoge fuga fugu hogo`.match(/fu\w*/);
[ 'fuga', index: 10, input: 'hage hoge fuga fugu hogo' ]
// /gを付けると、マッチする文字列全てを取得可能
> `hage hoge fuga fugu hogo`.match(/fu\w*/g);
[ 'fuga', 'fugu' ]

これらのフラグですが、組み合わせて使うことが可能です。ということで、/m と /gを組み合わせると下記のような結果になります。

  console.log('今回マッチさせたいもの');
  const input = `
Mimages/not_match.jpg
M       images/not_matchjpg
M       images/need_match.png
M       images/not_match.ts
D       not_match.jpg
R100    images/sample.jpg       not_match.jpg
A       need_match.jpg
M       need_match.png
A       &&HOGE/need_match.png
`
  const re = /^[AM]\s.+\.(?:jpg|png)$/gm;
  const files = input.match(re).map((line) => {
    return line.replace(/^[AM]\s*/, "")
  });
  console.log(files);
今回マッチさせたいもの
[ 'images/need_match.png',
  'need_match.jpg',
  'need_match.png',
  '&&HOGE/need_match.png' ]

ということで、欲しいデータだけ取得することができました。