tr-ikymのブログ

システムエンジニアをする者のブログです

exprコマンドは正規表現の(...)は使えない?-> 拡張正規表現ではないみたい。

仕事でexprコマンドと正規表現を使おうとして、やや詰まったので備忘録。

文字列長を返す場合

exprでよくやる使い方として、正規表現にマッチする文字列数を返す方法があると思います。

例えば、

$ expr "123" : '^[1-3].*$'
3

正規表現^[1-3].*$に文字列123がヒットするので、123の文字列数3が返ります。

文字列長が返らないパターン?

同じように、正規表現にマッチする文字列の長さが返ることを期待して、

$ expr "A123" : '^(A|B)[1-3].*$'

と書いてみました。 (A|B)という部分は、AかBでマッチすることを期待しています。

実行してみると、

$ expr "A123" : '^(A|B)[1-3].*$'
0

あれ?どこにもマッチしていない?

ちょっと悩んで、試しにエスケープを入れてみましょう。

$ expr "A123" : '^¥(A¥|B¥)[1-3].*$'
A

今度は数字ではなく、Aという文字列が返ってきました。

一体どうなっているのでしょうか?

exprでは(...)がそのまま使えない。

どうやらexprコマンドは、拡張正規表現ではなく基本正規表現に則っているようです。

正規表現 - Wikipedia

ただ(...)と書くだけでは、そのままの意味として扱われます。

$ expr "(A|B)123" : '^(A|B)[1-3].*$'
8

文字列(A|B)123の文字列長は8で、正規表現^(A|B)[1-3].*$にマッチしています。 (A|B)には「AまたはB」という意味はないことになっています。

ただ今回は、(...)をそのままの文字として使いたいのではありません。

基本正規表現ではエスケープするのが正しいようです。 (|にもエスケープしてます。)

# Aが返る
$ expr "A123" : '^\(A\|B\)[1-3].*$'
A

# Bが返る
$ expr "B123" : '^\(A\|B\)[1-3].*$'
B

# nullが返る?
$ expr "C123" : '^\(A\|B\)[1-3].*$'

確かに、「AまたはB」となっているようです。返ってくる文字が1文字ですが(笑)。

普段から拡張正規表現に慣れていると、そのまま(...)を使ってしまいますね。

では次に、どうして正規表現にマッチする文字列長ではなく、特定の文字列が返るのでしょうか?

\(...\)内にマッチした文字列が返る。

これに関しては、

man expr

と打てば答えが見つかりました。以下、引用です。

Pattern matches return the string matched between \( and \) or null; if \( and \) are not used, they return the number of characters matched or 0

なんと。。。整理すると、

確かに先の例はこれに従っていますね。

# 正規表現にマッチした文字列長が返る。
$ expr "A123" : '^A[1-3].*$'  
4

# 正規表現にマッチしているので、\(...\)の中身にマッチするものが返る
$ expr "A123" : '^\(A\)[1-3].*$' 
A

ちなみに、正規表現でのA or Bを実現しつつもマッチした文字列全体を返す方法はないのでしょうか?

\(...\)で囲えば返す文字列を操作できる。

出来ました。

入力文字だけ変えて、正規表現の部分は同じです。

\(A\|B\)[1-3].*\(...\)で囲んでいます。

$ expr "A123" : '^\(\(A\|B\)[1-3].*\)$'
A123

$ expr "B123" : '^\(\(A\|B\)[1-3].*\)$'
B123

可読性が悪いですね。。。笑

jqコマンドは抽出だけでなく、JSONの編集もできる。

AWS CLIJSONをインプットしようとした時のこと。

JSONファイルを一度作って、エディターで編集する」みたいなことをしたくなかったので、 どうにかコマンドラインだけでJSONを編集出来ないかと調べていたら、jqが使えることが分かった。

これまでもjqコマンドは使っていたが、データ抽出くらいにしか使っていなかった。 JSONの編集もできるとは知らなかったので、備忘録として本記事を書く。

はじめに

jqコマンドがどんなものかは、下記のサイトを参照。

https://stedolan.github.io/jq/

JSONからのデータ抽出などのような基本的は使い方に関しては多くの記事があると思うので、他を参考にしてほしい。

この記事は、jqコマンドによるJSONの編集にフォーカスして、いくつか例を挙げる。

例1: 特定keyに紐づくvalueを上書き

|= を使用する。

$ echo '{"name": {"first": "hoge"}}' | jq '.name.first|="poyo"'
{
  "name": {
    "first": "poyo",
  }
}

なお、存在していないkeyを指定すると上書きではなく追記となる。

$ echo '{"name": {"first": "hoge"}}' | jq '.name.last|="fuga"'
{
  "name": {
    "first": "hoge",
    "last": "fuga"
  }
}

例2: 特定keyとそのvalueを削除

del() を使用する。

$ echo '{"name": {"first": "hoge"}}' | jq 'del(.name.first)'
{
  "name": {}
}

例3: 別のJSONの要素にする

map()する。なお、例のJSONの入力は配列となっている。

$ echo '[{"first": "hoge"}]' | jq 'map({name: .})'
[
  {
    "name": {
      "first": "hoge"
    }
  }
]

例4: 配列を一つにする

+ する。例が長くなるのが嫌なので、簡単に数字を例に。

$ echo '{"first": [1,2], "second": [3,4]}' | jq '.first + .second'
[
  1,
  2,
  3,
  4
]

他の使い方は

この記事では、jqコマンドを使用したJSONの編集ができる例をいくつか取り上げた。 jqコマンドのポテンシャルはとても紹介しきれないので、下記にあげるようなところを参考にしてほしい。

もしくは、

man jq

と打ってマニュアルを読むか。

qiita.com qiita.com