それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

jq コマンドで json を minify する

jq を使って minify することが出来る

cat composer.json | jq -c . 

または

jq -c . < sample.json

任意のJSONJSONのフィルタを変えて小さくすることが出来る。

サンプル

composer.json を小さくする
{
  "repositories" : [
    {
      "type" :"git",
      "url" : "https://github.com/takuya/simple-app.git"
    }
  ],

  "require":{
    "takuya/simple-app": "dev-master"
  }
}

出来た。

 jq -c .  < composer.json
{"repositories":[{"type":"git","url":"https://github.com/takuya/simple-app.git"}],"require":{"takuya/simple-app":"dev-master"}}

pythonのORM:SQLAlchemy の基本的な使い方

SQLAlchemy を使ってみる。

目次

インストール

pip install sqlalchemy

テーブルの作成

テーブルを作成するときは、ORMを使うと便利。

Baseを継承して 、カラムをメンバに定義したうえで、 Base.metadata.create_all(engine) を呼び出す。

from sqlalchemy import * 
from sqlalchemy.orm import *


Base = declarative_base()
class Page(Base):
  ##テーブル名
  __tablename__ = 'pages'
  ## カラム名
  id   = Column(Integer, primary_key=True)
  url  = Column(Text, unique=True, nullable=False)
  html = Column(Text , nullable=True)
  http_status = Column(Integer, default=0, nullable=True )
  status = Column(Integer, default=0, nullable=True )
  ## メソッドもここに書く
  def is_not_found : 
       return  self.http_status == 404;

engine = create_engine('sqlite:///./urls.db', echo=True)
Base.metadata.create_all(engine)

手順のおさらい

  1. 必要なパッケージをimport
  2. Base=declarative_base()を使う
  3. RDBMSに接続する
  4. create_all する

RDBMS への接続

engine = create_engine('sqlite:///./urls.db', echo=True)

echo=True にすると接続と実行ログが出るので重宝するので、初回実行は必ずTrueにしておく。

テーブル定義のクラス。

class Page(Base):
  __tablename__ = 'pages'

  id   = Column(Integer, primary_key=True)
  url  = Column(Text, unique=True, nullable=False)
  html = Column(Text , nullable=True)
  http_status = Column(Integer, default=0, nullable=True )
  status = Column(Integer, default=0, nullable=True )

メンバ変数で入れていく。

テーブル名を __tablename__ で代入する。

カラムには、Column( TYPE, *options ) で指定する。

TYPEには sqlalchemy.Integer sqlalchemy.String 等がある。

Base.metadata.create_all() は複数回呼び出しても大丈夫。なかったら作るだけ。既存のテーブルを消したり、再定義しない。

INSERT/UPDATE をする。

データの投入はクラスを使うと便利。

ORMを使うためクラスのインスタンス変数をメインにいじっていく。

また、接続はすべてSession単位で扱われる。

SessionはBegin / Commit を抽象化したものだと思えばいい。

INSERT INTO をするには Page( url=i ) で クラスインスタンスを作成し、session.add(page) でレコードを追加する。

そして、最後にsession.commit() で コミットする。

INSERT の例

Session = sessionmaker(bind=engine)
session = Session()

url = 'https://web.wm.auone.jp/lx/regular/?p=2'

page = Page( url=i  )

ret = session.add(page)

session.flush()
session.commit()

UPDATE について

UPDATE 句については、クラスのメンバ変数を更新し、最後にCommitする。

page = session.query(Page).filter( Page.html == (None) ).first()

page.html =  fetch( page.url )
page.http_status = 200
page.status = 1
session.commit()

クラスインスタンスを作成する代わりに、Fiter( SELECT ) でオブジェクトとしてレコードを取り出してから、メンバ変数を更新し、最後に COMMITする。

また、UPDATEは、条件にマッチするものを一括で更新することも有りえます。

session.query(Page).filter( Page.id < 100 ).update( {status=0} )

DELETEする。

DELETEにも2つの考え方があります。ひとつはオブジェクトをレコードをから消す。もう一つは検索条件にマッチするものを消す。

オブジェクトを取得して消す場合

page = session.query(Page).filter( Page.id == 1 ).one()

session.delete(page)
session.commit()

検索条件にマッチするものを消す場合。

session.query(Page).filter( Page.id == 1 ).delete()

INSERT・UPDATE・DELETE のキャンセル

セッションは begin 同等なので キャンセルできる。

session.rollback()

SELECT について。

テーブルからデータを取り出すには、SELECT句をWrapした query を使う。

またWhere句をWrapしたfilter / filter_by を使う。

pages = session.query(Page).all()
page = session.query(Page).first()

query した後に all ()first() を呼び出すことが必要。 これらのメソッドを実行することでPreared statement が実行される。

all () / first() とその仲間

SELECTをするためには、 query().filter().all() のように書くことが出来る。 all () の他には次のようなものがある。

レコードを返すメソッド一覧
  • all () 配列を返す。
  • first () クラス(またはNone)を返す。
  • count () 個数を返す
  • one 系
    • one() filterの結果が1つだけになることを期待する。
    • one_or_none() one と同じ
    • scalar() ORMマッピングしないときに使う。
one()について

one は考え方が、少し面倒ですね。

one() は filterの結果が1つだけになることを期待する。つまり、where で絞り込んだ結果が複数返ってきたら sqlalchemy.orm.exc.MultipleResultsFound を返してエラーになる。また、一つも見つからない場合は sqlalchemy.orm.exc.NoResultFound を返す。

one_or_none について

one_or_none は 結果が0件のときにonesqlalchemy.orm.exc.NoResultFound 例外を送出する代わりに None を返す。

scalar について

scalar()one() を内部的に実行する。

これは scalar 値を取り出すのに使う。たとえば、 select count(id)select id from tableを実行するときに使う。実際にはcount ()が あるしORM使うしあんまり使わないかも?

count について

count() は COUNT(*) を実行してくれる。

個数が少ないなら all() してlen()見ても良いのですが。count の方がいいね。

filter の書き方例

where句の代わりにFilter()を使ってデータを取り出す。

session.query(Page).filter( Page.id == 1 ).one()

これは、クラスに入れてある 変数 id が Colmun( ) を継承していて Column の演算子オーバーロードを使っているので クォートなしに書ける。詳しくは dir( Your_ORM_Class.colname ) などすればわかる。

この演算子オーバーロードのお陰で記述がクオートなしでスッキリする。こういうのはPHPRubyのORMにも真似してほしい所。

IS NULL / IS Not NULL

IS NULL は == None と解釈する。 IS Not NULL は != None と解釈する。

次のように書く

session.query(Page).filter( Page.html == None ) # is null
session.query(Page).filter( Page.html != None ) # is not null

演算子オーバーロードが嫌いな人は、次のように関数で書くことが出来る。

session.query(Page).filter( Page.html.is_(None) ) # is null
session.query(Page).filter( Page.html.isnot(None) ) # is not null
COLNAME = VALUE / COLNAME > VALUE

where 句の基本 COLNAME = VALUEは is null で見たように、 == VALUE で書く。

is not nullの書式を覚えてしまえば、他も全て同じに解釈できて便利です。

session.query(Page).filter( Page.id == 1 ) 
session.query(Page).filter( Page.id != 1 ) 
session.query(Page).filter( Page.id < 10  )
session.query(Page).filter( Page.id >= 10  )
session.query(Page).filter( Page.id == None )  # is NULL 

ちなみに、経験則ですが、ORMの使用法を見るとき、is not nullLIKE を見るといい、そこにORMの特徴が出るのでis not null / like を調べればだいたい使い方がわかる。

AND について

ANDは、条件を複数突っ込めば ANDになる。とてもかんたん。

session.query(Page).filter(  Page.id > 51 , Page.id < 100  )
LIKE 句について

LIKE は、直接文字列を突っ込みますね。% が Python の文字列フォーマット指定子なので上手に使ってください。

query.filter(Page.url.like('%search_string%'))

フォーマットするときは

search="search_string"
query.filter(Page.url.like( "%%%s%%" %   search )) # お世辞にも読み易いと言えない
直接SQLのWhere句を入れたい

Where句は複雑になるので、ORMに頼らずに直接指定したい場合。此の目的にはsqlalchemy.text が使える。

session.query(Item).filter( text(' id < 10 ' ) ).all()
session.query(Item).filter( text(' id > 10 and id < 12 ' ) ).all()

この場合はPrepareを経由せずに実行されるので、SQL Injection の可能性がある。そのためbindParam するには次のようにする。

プリペアドステートメントの使い方は、RDBMSによって多少異なるが、大抵はプレイスホルダを ?で書くか、 :param_name のようにコロンで書く

sqlalchemy.text() では :name は使えるが ? は使えないみたい

session.query(Item).filter( text(' id > :value1 and id < :value2' ) ).params(value1=10, value2=12).all()

これは動かなかった。( 追加調査が必要 かも

items  = session.query(Item).filter( text(' id = ? ' ) ).params(1)

SQL Injection を防ぐためには text() をで直接使うなというのではなく、ユーザーから入力される文字列に対して bind を実行するのである。

limit / offset

これは予想通りの記述。

session.query(Item).limit(10).offset(10)

ORDER BY / ORDER BY DESC

ORDER BY は かんたん。でも 降順はちょっとめんどくさい

ASC
session.query(Item).lorder_by( Item.id )
DESC
session.query(Item).order_by( Item.id.desc() )
session.query(Item).order_by( desc(Item.id) )
session.query(Item).order_by( " id desc " )

order_by_desc/ order_by_asc みたいな関数でも良いと思うんだけど、なかったので上記のように書く必要がある。

既存のテーブルを扱うクラスについて

既存のテーブルを扱うときは、必要なカラム分だけメンバ変数に取り出してもいい。全カラムを指定するのは流石に面倒だろうし。

from sqlalchemy import * 
from sqlalchemy.orm import *


Base = declarative_base()
class Item(IBase):
  __tablename__ = 'pages'

  id   = Column(Integer, primary_key=True)
  status = Column(Integer, default=0, nullable=True )

もちろんマッピングしてないカラムについてはORMから見えなくなる。

まとめ

ORMとして非常に書きやすいと思います。今回は sqlalchemy.ormがメインで、sqlalchemy.sql.expression については殆ど言及してないです。

expression を使ってもSQLを組み立てるより手軽でかなり使いやすいのでおすすめです。

たとえば、updateについては ORMを使わずに次のように書くことが出来ます。

公式ドキュメントからの引用

update(users).where(users.c.id==5).values(name='user #5')
users.update().where(users.c.id==5).values(name='user #5')

どんな使い方をしたとしてもSQL/ RDBMS を扱うには SQLAlchemy は期待にかなう優秀なツールですね。

参考資料

http://www.sqlalchemy.org/library.html#reference

http://stackoverflow.com/questions/4186062/sqlalchemy-order-by-descending

DBアクセス抽象化で、INSERT と UPDATE でコードを共通化させるハック。

困りごと: INSERT と UPDATE の共通化をしたい。

似たようなコードを何度も書くのはめんどくさい。テーブル定義が変わったときにもう鬱陶しい。

共通化したい・・・

function insert( name , date ){
    sql = "INSERT INTO table_name VALUES( #{name} , #{date} );"
    mysql->execute();
}
function update( name , date , id ){
    sql = "UPDATE table name_name=#{name}   modified=#{date}  ) where id = #{id} ;;"
    mysql->execute();
}

解決方法を見つけた。

いろいろな設計を見ててたら、次のようにすると解決した。

さきに、空っぽのレコードを作ってしまう。

function save( name , date , id=null){
    if ( ! id ) {
         sql = "INSERT INTO table name VALUES( );"
         mysql->execute(sql);
         id = mysql->last_insert_id();
     }
    sql = "UPDATE  table_name set   name=#{name}   modified=#{date}  where id = #{id} ;"
    mysql->execute(sql);
}

ものすごいバッドノウハウで臭いカンジがするんだけど。意外とコレでスッキリするんだよなぁ、

どうでもいい話

データ・ベースのアクセスってSQLのbuildingで四苦八苦したり、Prepareと相性が悪くて、なぜかStringを結合したりとかしちゃってるのを見たことがあってですね。

ORMを使えば良いんだろうけど、どのORMを採用するかで宗教戦争になりそうだったり、 SQLでこう書くのを、ORMでどう書くかと翻訳が2段階になって、時間コストが増えたりとかですね。

ORMは遅かったり最適化がうまくできん買ったりめんどくさいですよね

phpのリクエストパラメータをサクっと処理する

phpのリクエストを読むのに、ブラケットがめんどくさい

ブラケット書くのがめんどくさい 此の記述がもはや狂気。

<?php

if ( empty($_GET["name"]) ){

}

["name"] を書いて、if ( func( ) ) に加え、 $_GET などと、どれだけ大変なのだ。キーボードの運指が辛い。

かといって、フレームワークのやり方もキレイとは言えるのか?

<?php

$name = Request::input('name', 'default_name');

リクエストをオブジェクトにして取得するのはもう面倒だ。

コレも長過ぎる。そもそも変数名に出来ないところがもうイヤだ。

考えてみた

if のチェックは3項演算子で省略できる。ブラケットとクオート地獄はオブジェクトにキャストしてしまえばいい。さらに、デフォルト値は配列を置換する形式にしておけば、余計な文字列を処理して必要なものだけを取り出せるはずだ。

<?php

if ( $req->name ) {
   echo $req->name
}

こんなふうに書けたら楽なのに。。。

作ってみた。

ポイントは3つ

  1. デフォルト値を尊重する
  2. 配列と配列のマージ・置換処理をする
  3. オブジェクトにキャストする

コレで十分なはずだ。

<?php
$defaults = [
  'limit' => 10,
  'offset' => 0,
  'do_search'=>0,
  'search' => null,
  'station' => null,
  'category_id' => null
];


$req = array_merge($defaults, $_REQUEST);
$req = array_intersect_key($req, $defaults);
$req = (object) $req;

こうしておけば、使うときも楽ですよね

before

<?php 

if(   empty($_GET["limit"]) ||  ($_GET["limit"]) < 0  ){
    $_GET["limit"] = 100;
}
if(   empty($_GET["name"]) ||  strlen($_GET["name"]) < 1  ){
    $_GET["name"] = 'default_name';
}

もう悪夢でしか無い。この記号の多さは。

after

<?php 

$req->limit  = $req->limit >0 ?: 100;
$req->name  = strlen($req->name) >0 ?: 'default_name';

うん、随分楽になる。phpフレームワークさんたちは、これを隠蔽する必要あるのかな・・・

指定したファイルの日付を取得してフォーマットするシェルコマンドdate

date コマンドでファイルの日付を取得する。

指定したファイルの日付を取得するには

$ date  +'%F %T' -r /etc/aliases
2015-08-02 13:35:20

最終更新日が取得できる。

     -r, --reference=FILE
              display the last modification time of FILE

もちろん stat でも構わない。

$ stat -c %y /etc/aliases
2016-10-16 01:23:47.000000000 +0900

でもファイル日付が合わない。。。。みたいなことがある。

これは、stat は シンボリックリンクデリファレンスせずに、symlink を読み取るので注意が必要。

date -r と同等のstatを行うには次のようにする。

$ stat -L -c %y /etc/aliases
2015-08-02 13:35:20.000000000 +0900

stat は w/x/y/z とおぼえやすい並びになってる

フォーマット 意味
%w time of file birth, human-readable; - if unknown
%x time of last access, human-readable
%y time of last data modification, human-readable
%z time of last status change, human-readable
$ stat -c %w /etc/aliases
2016-10-16 01:23:47.000000000 +0900
$ stat -c %x /etc/aliases
2016-10-16 01:23:47.000000000 +0900
$ stat -c %y /etc/aliases
2016-10-16 01:23:47.000000000 +0900
$ stat -c %z /etc/aliases
2016-10-16 01:24:48.000000000 +0900

でも覚えるのめんどくさいので、date -r は便利だと思う。

dateコマンドでiso/rfc日付時刻タイムゾーンをぱぱっとフォーマットして作る

date コマンド便利だけど、フォーマット指定がめんどくさい

takuya@:~$ date --date "2017-03-22 + 90days"  +"%Y-%m-%d %H:%M:%S"
2017-06-20 00:00:00

ああ、めんどくさいよ%Y-%m-%d 、記号が多くてめんどくさい。

よくつかう日付は、オプションが用意されている

$ date  -I
2017-03-22

よく使う日付のフォーマットもショートカットが用意されている。

$ date  +'%F'
2017-03-22

$ date  +'%T'
15:31:54

$ date  +'%F %T'
2017-03-22 15:30:49

時刻を入れるなら、私達が日常使いではコレで十分だが、UNIX時刻でいくならタイムゾーンがないとおかしくなるので、DBなどはタイムゾーンを入れたほうが無難。

-I は更に使える

さらによく使うタイムスタンプにもショートカットが用意されている

$ date -Iseconds
2017-03-21T14:42:20+0900

スペースは駄目だよ。date -I seconds は無効

または、次のようにしてもほぼ同じである。

date --rfc-3339=seconds
2017-03-21 14:43:47+09:00

rfc 3339 のフォーマットの例

rfc 3339 は私達が見ても結構わかりやすい。rfc 3339の場合はスペースをいれる。

ISOとRFCでオプションの渡し方が違うので注意

$date -R date
2017-03-22

$date -R seconds
2017-03-22 15:44:26+09:00

$date -R ns
2017-03-22 15:44:26.468367829+09:00

ロングオプションなら

$date --rfc-3339=date
2017-03-22

$date --rfc-3339=seconds
2017-03-22 15:38:04+09:00

$date --rfc-3339=ns
2017-03-22 15:38:04.553941046+09:00

-I オプションの例

$ date -Idate
2017-03-21

$ date -Ihours
2017-03-21T14+0900

$ date -Iseconds
2017-03-21T14:47:08+0900

$ date -Ins
2017-03-21T14:47:11,948607361+0900

ロングオプションなら次の通り

$ date --iso-8601=date
2017-03-22

$ date --iso-8601=hours
2017-03-22T15+0900

$ date --iso-8601=minutes
2017-03-22T15:36+0900

$ date --iso-8601=seconds
2017-03-22T15:36:17+0900

$ date --iso-8601=ns
2017-03-22T15:36:17,095634462+0900

ナノ秒を含めたタイムスタンプを作るなら

$date --rfc-3339=ns
2017-03-21 14:46:40.646812475+09:00
$ date --iso-8601=ns
2017-03-22T15:36:17,095634462+0900

などとすると便利。

その他のDateコマンドの使い方はこちら

date コマンドで「昨日」などの日付を計算するとき

date コマンドで相対日付で目的の日をパパっと計算する。 - それマグで!

date コマンドのオプションについて

dateコマンドを見なおしてみる。dateコマンドオプションは使える - それマグで!

利用したdateコマンドについて

dateコマンドはいくつか実装があるので、若干の差異が有ります。利用する前にコマンドがGPL/BSDで違いがある。

takuya@:~$ date --version
date (GNU coreutils) 8.23
Copyright (C) 2014 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

作者 David MacKenzie。
takuya@:~$

bashで複数行の文字列の代入と、ヒアドキュメントを使わずに変数に格納する方法

bash で複数行の文字列を変数に格納する方法

「改行が入らない」ではなく、「改行が見えない」のです。入らないと思って改行を調べてしまう事例が多いです。注意しましょう

その1:一番シンプルなのはそのまま

str="
ここは
サンプル
の複数行文字列
です。
"
echo "$str"

echo $str ではなく、 echo "$str" なのがちょっと味噌ですね

その2:ヒアドキュメント編

一般的なのは、ヒアドキュメントをつかって定義する方法。インターネットを探すとコッチが多いんだけど、なぜわざわざcatを呼び出す無駄なことをしているのか理解できない。dash/bash/zsh でもサンプル1で動くのに。

str=$(cat <<EOS
ここは
サンプル
の複数行文字列
です。
EOS
)
echo "$str"

なぜ "$str" とクオートが必要なのか?

クオートが必要なのは、改行を含めた文字列が「変数に格納」されていて、出力時に「分割」されるからとかんがえられる

次のように定義したとき

str="a
b
c

出力前に、先に変数展開がされます。

echo $str

echo a 
b 
c

となり、IFSで分割されて

echo a b c 

となってると考えられます。

これを確かめるために、IFSをオフにして実験してみます。

実験:IFS をオフにして "$str" を不要にした。

str="
ここは
サンプル
の複数行文字列
です。
"
IFS=''
echo $str

ifs でbash の区切り文字を変えることがポイント

実行結果

takuya@2017-03-21#7$ bash   sample.sh

ここは
サンプル
の複数行文字列
です。

此の実験結果から分かる通り、変数には改行付きで格納されてることがわかります。

bash はコマンドを1行読み込むと、変数展開をします。展開後文字列にIFSを適用し、実行する。そのため一般的に"$str" とダブルクオートで囲む解決策が頻用されると考えれます。

その3:ヒアストリング

コマンドにヒアストリングを渡すことが出来ます。

cat <<< "here string"

これを応用して、次のように書けます。

str=$(cat <<<"
ここは
サンプル
の複数行文字列
です。
")
echo "$str"

インデントもしたい

ソースコードの可読性向上にインデントを企図してるときは、ヒアドキュメントが有能です。

cat <<-EOF
    ここは
            サンプル
            の複数行文字列
            です。
    EOF

-EOF と、ダッシュをつけることで先頭のタブ文字を捨ててくれます。タブ文字だけですよ?expandtab しててスペースはに置換してたら動きません。

繰り返しになりますが、インデントに使えるのはTAB文字だけですよ?

bash 4 からの新機能 mapfileを使う場合

StringIO#lines 的なことで解決する場合

mapfile str <<EOF
ここは
サンプル
の複数行文字列
です。
EOF

echo "${str[@]}"

MapFileは、ファイル(この場合はstdinのfd)を複数行に分割し、配列に格納してくれます。これを使うともっと楽にできます。

注意 パイプはできない

次のパイプはエラーになります。

echo "aa
bbb" | cat 

パイプに書くときは一旦変数に入れます。

str="aaa
bbb
"
echo $str | cat 

参考資料

2021-02-16

ifs で検索されないので ifsを明示的に指定した

2022-04-18

パイプについて追記。

sendmail でコマンドからメールを送信する。

メール通知のテストに使う。

postfix や exim4 のメールサーバーの設定をしていて、テスト・メールを送信したいときに、手作業でsendmail コマンドを送るのは、ちょっと面倒なのと、どのメールが未到達で、どのメールが到達したか区別するのが大変なのでスクリプト書きました。

サンプルスクリプト

サンプルスクリプトでは、連番を入れて、どのメールか識別できるようにしてて、日付と時刻をいれてそちらでも識別できるようにしておいた。

send_mail.sample.sh

#!/usr/bin/env bash


function counter() {
  if [[ !  -f /tmp/count ]] ;  then
    touch /tmp/count
    echo 1 > /tmp/count
  fi
  num=$(tail -n 1 /tmp/count)
  echo $(( num+1 )) > /tmp/count
  echo $num
}



ifs=$IFS
IFS=''
text="\
To:takuya@example.com
Subject: Hello, From $(hostname)

Hi, can you see me ?

num:$(counter)


---
takuya
 $(date --rfc-3339=seconds)
"


echo  $text
echo -e $text | sendmail -i -t

2019-12-13

タイプミスを修正

sudoersの設定を分割する /etc/sudoers.d を使おう

/etc/sudoers.d で分割できる。

/etc/sudoers.d を使うと、必要なものを覚えやすいファイル名で書いておいてバージョン管理することが出来る。

編集方法

編集方法がめんどくさい。分割して管理できるが、各ファイルを編集するのにvisudoを使う必要があり、これがめんどくさい。

visudo -f /etc/sudoers.d/my_conf

ただしく読み込まれたか

もし直接パスを指定して、編集し、手作業で読み込んだら、一旦パースしてテストするしか無いかもね。

sudo vim  /etc/sudoers.d/my_conf
visudo -x - | jq  

読み込む仕組み

どのタイミングで読み込むのかは /etc/sudoers を見ればわかります。

/etc/sudoers

sudoersには次のように書いてあります。最終行の #includedir に注目。 なのですべての一番最後に読み込む様になっています。

#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# Cmnd alias specification

# User privilege specification
root    ALL=(ALL:ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL
takuya  ALL=(ALL) NOPASSWD: ALL

# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d

#includedir はコメントじゃないの?

コメントではありません。

#includedir /etc/sudoers.d  # ←これは正しい
includedir /etc/sudoers.d  # ←これは間違い

一見すると、コメントに見えて、ハッシュ(#)を消そうと思うかもしれませんが。これは明確にディレクティブですね。*1

sudoersのファイルを見ていると、コメントでは、 '# ' と空白文字を入れて区別しているようですね。

# comment 
#include

参考資料

https://wiki.debian.org/sudo

nmtui-connectで無線LAN接続して、NetworkManagerと仲良くなる

Pizeroで無線LANを接続する

Wi−Fiにぱぱっとつなぐには、アレコレとコマンドを弄るより network-manager を使うのも楽ちんだと思う。

sudo nmtui-connect

画面が出てくる

f:id:takuya_1st:20170318015150p:plain

ssh 経由でも安心

細かいコマンドや /etc/network を使わなくていいので楽ちんですね。

コマンドに慣れてない raspberry pi ユーザには良いですね。

POSIX の メッセージ・キューを作成する(C言語)

POSIXのメッセージ・キューをC言語で扱う

コンパイルには gcc -lrt を使う。

まずは送信するところから。

q_push.c

#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(){
  int cnt;
  int ret;
  char *str;
  void *pt;
  mqd_t q;

  q = mq_open("/sample", O_WRONLY | O_CREAT );
  while(1){
    sprintf(str ,"%d", cnt++);
    printf("%s\n",str);

    pt = calloc(strlen(str) + 1,sizeof(char));
    strcpy( pt, str );
    ret = mq_send( q, pt , strlen(pt) , 0    );

    if ( ret > 0  ) {
      perror("push error");
      return;
    }
    sleep(1);
  }
}

受信する所。

#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(){
  mqd_t q;
  struct mq_attr attr;
  void *buff;
  ssize_t n;
  char *str;

  q = mq_open("/sample", O_RDONLY | O_CREAT );

  while(1){
    mq_getattr( q ,&attr );
    buff = malloc(attr.mq_msgsize);
    //
    n = mq_receive( q, buff, attr.mq_msgsize,NULL);


    // printf("read: %d bytes \n",n);
    printf("%s\n", (char*)buff);
    usleep(1000*100);
    free(buff);
  }
}

コンパイル

$ gcc q_push.c -lrt -o q_push
$ gcc q_pop.c -lrt -o q_pop

python とやり取りしてみる

前に作った、Pythonposix_ipc を使ってキューを送信して受信してみる。

コンパイルしたバイナリで送信

takuya@:mq$ gcc q_push.c -lrt -o q_push
takuya@:mq$ ./q_push
1
2
3
4
5
6
7
8

python で受信

takuya@:mq$ python q_pop.py
113
114
115
116
117
118
119

異なる言語でデータ交換

JSON でデータのやり取りも良いんだけど、ファイルロックを考慮したり、名前付きパイプでもいいんだけど、複数ワーカーをぱぱっと動かせる感じなのは、とてもいいよね。いちいちSQLを持ち出してキュー構造を作るのは不便だし。

ワーカープログラムが死んでもキューは残ってるし。POSIXのファイルベースなAPIはホントわかりやすい。

参考資料

POSIX の メッセージ・キューを確認する。コマンドで。

POSIX message queue を確認する。

Linux なら /dev に出現する。

takuya@:~$ ls /dev/mqueue/

キューがあるとき

/sample という名前でキューを作ったのでキューを確認することが出来る。

takuya@:mq$ ls /dev/mqueue/
sample

キューの状態をサクッと確認する。

cat すれば中身が見られる。

takuya@:mq$ sudo cat  /dev/mqueue/sample
QSIZE:30         NOTIFY:0     SIGNO:0     NOTIFY_PID:0

キューを削除する

/dev/ から消せば、簡単にゴミを消せる。

参考資料

http://unix.stackexchange.com/questions/70837/linux-command-to-check-posix-message-queue

PythonでPOSIXのMessageQueue を使う。

POSIX の メッセージ・キューを使う。

プロセス間通信などと呼ばれる。POSIXのキューを使ってみる。

プロセス間通信 - Wikipedia

プロセスとプロセスのデータのやり取りにメッセージ・キューを使うことで幾分楽になる。マルチスレッド以前に複数プロセスでデータをやり取りすると複数分散して処理が楽になることもある。

動作例

youtu.be

ポイント

  • 送信側はドンドン送信できる。
  • 受信側は逐次読み込んで取り出せる。
  • データが空っぽなら、受信側はブロックされる。

受信側は、なにもしなくても受信不可ならブロックされるので楽ちん。感覚的には、空っぽのパイプと同じですね。

インストール

python で使うには posix_ipcを使う

pip install posix_ipc

Queueにメッセージを追加

#!/usr/bin/env python

import json
import posix_ipc

def main () :
  mq = posix_ipc.MessageQueue("/my_q01", posix_ipc.O_CREAT )
   #
  cnt = 0
  #
  while True:
    cnt = cnt + 1
    obj = { "counter": cnt }
    mq.send( json.dumps( obj ) )
    print("pushed: %d" % cnt )
    time.sleep(1)


if __name__ == "__main__" :
  main()

Queue からメッセージを取り出す

#!/usr/bin/env python

import json
import posix_ipc

def main () :
  mq = posix_ipc.MessageQueue("/my_q01", posix_ipc.O_CREAT )
  # 
  while True:
    data = mq.receive()
    print( data[0] )
    time.sleep(1)

if __name__ == "__main__" :
  main()

Queue を削除する

#!/usr/bin/env python
import posix_ipc

mq = posix_ipc.MessageQueue("/my_q01")
mq.unlink()

コマンドからも確認できます。

takuya-1st.hatenablog.jp

参考資料

Semanchuk.com - POSIX IPC for Python - Semaphores, Shared Memory and Message Queues

シェルで指定文字に文字列を分割して結合する。

文字列をシェルで、指定文字数に分割して結合するには

選択肢になるコマンドは、分割・結合でそれぞれ次の通り

  • 分割
  • 結合
    • paste
    • tr

などが使えることがわかった。

分割の例
fold -w2
grep -E -o '.{2}'

文字列を指定文字に分割する。fold コマンド

fold コマンドが便利。ruby であればsplit/grep などで出来る。

takuya@~$ echo $(openssl rand -hex 6) | fold -w1
0
3
3
9
5
2
f
0
7
3
1
e

fold -w コマンドは、指定バイトで切り出す。

takuya@Desktop$ echo $(openssl rand -hex 4) | fold -w4
d0ac
01f3

指定バイトなので日本語のようなユニコード文字列は3バイトで切り出す。

takuya@~$ echo ああああ | fold -w 3
あ
あ
あ
あ

grep でもなんとか出来る

 echo $(openssl rand -hex 4) | /bin/grep -i -E  -o  '.{2}'
7f
1c
10
ec

おお。grep 万能説!

さて、分割した文字列を結合するには

join かと思いきや、paste を使うと便利だった。

paste -sd ':'

paste で複数行を結合

takuya@Desktop$ cat - | paste -sd ","
a
b
c
a,b,c

使いみち

xdd/hexdump/od 何かと組み合わせると便利かも

たとえば、画像のdumpをちょっと見る。

takuya@Desktop$ xxd -p  -l 100 logo.png  | ~/.bin/grep -i -E  -o  '.{2}' | paste -sd ","
89,50,4e,47,0d,0a,1a,0a,00,00,00,0d,49,48,44,52,00,00,00,60,00,00,00,2d,08,03,00,00,00,66,80,9e,b8,00,00,00,19,74,45,58,74,53,6f,66,74,77,61,72,65,00,41,64,6f,62,65,20,49,6d,61,67,65,52,65,61,64,79,71,c9,65,3c,00,00,03,12,69,54,58,74,58,4d,4c,3a,63,6f,6d,2e,61,64,6f,62,65,2e,78,6d,70,00,00,00,00,00

例えば、MACアドレスっぽいものを作る

takuya@~$ echo $(openssl rand -hex 6) | fold -w2 | paste -sd ':'
7e:25:cb:0b:70:ef

参考資料

http://stackoverflow.com/questions/15553493/how-to-print-only-the-hex-values-from-hexdump-without-line-numbers-or-ascii-tabl

http://stackoverflow.com/questions/2764051/how-to-join-multiple-lines-of-file-names-into-one-with-custom-delimiter

http://stackoverflow.com/questions/7578930/bash-split-string-into-character-array

Debian/ubuntu のapt自動アップデートのUnattendedUpgradesを設定する

Debian の apt 更新がめんどくさいのでなんとかする。

昔は cron-apt でやってたけど、今時はどうするのかなーと思って調べてたらUnattendedUpgradesがあったのでコレを設定することに。

Debian Wikiに従ってインストール

sudo apt install unattended-upgrades apt-listchanges

apt-listchanges は無くても良さそう。apt-listchangesはメールで更新の通知をするツール。

アップデートを有効にする

sudo dpkg-reconfigure -plow unattended-upgrades

アップグレードを有効にする。

sudo -e /etc/apt/apt.conf.d/20auto-upgrades

定期的にアップグレードとパッケージリストの更新を有効にする。

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

自動更新の確認

sudo unattended-upgrades --dry-run

dry-run で自動更新の設定と動作をチェックできる。

更新するパッケージの指定と除外はここで設定できる

takuya@orangepizero:~$ cat  /etc/apt/apt.conf.d/50unattended-upgrades | head
// Unattended-Upgrade::Origins-Pattern controls which packages are
// upgraded.
//
// Lines below have the format format is "keyword=value,...".  A
// package will be upgraded only if the values in its metadata match
// all the supplied keywords in a line.  (In other words, omitted
// keywords are wild cards.) The keywords originate from the Release
// file, but several aliases are accepted.  The accepted keywords are:
//   a,archive,suite (eg, "stable")
//   c,component     (eg, "main", "crontrib", "non-free")

設定が不安なら dpkg-recongfigure でも出来る

sudo dpkg-reconfigure -plow unattended-upgrades

no を選ぶと、/etc などのファイル更新でどっちを優先するか決められる。

設定ファイル更新されるの嫌だから、ローカル優先しておいた。

もし更新するパッケージを指定するなら

以下のファイルにアレコレ書いて、更新するパッケージを指定する。 デフォルトで大丈夫だと思うのですが。更新したら動かなくなる野良ビルドドライバとか入ってたら linux-header とかカーネルの更新は避けたいところだったりする。

sudo -e /etc/apt/apt.conf.d/50unattended-upgrades

たとえば、gitlab-ce の パッケージを指定した例。

gitlab-ce をapt に行を追加していて、ominibus パッケージとして使っている場合

// Automatically upgrade packages from these (origin:archive) pairs
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}:${distro_codename}-updates";
    // この行を追加
    "o=*packages.gitlab.com/gitlab/gitlab-ce,codename=${distro_codename}";

このようにすると apt に追加した gitlab のパッケージが自動更新の対象になる。

もし自身でapt に追加していたらこのようにする。通常は debian の updates / security を自動インストールしてくれる。

除外したい場合はブラックリストにパッケージ名を書く

アップデートの実行ログ

アップデートの実行ログは /var/log/unattended-upgrades/unattended-upgrades.log に書かれる。

また、dpkg のログが /var/log/dpkg.log に記載されるので、自動更新のヒストリ(履歴・実行記録)をたまに見てやるといい。

参考資料

2021-11-23

ログについて記述を追加