メモの日々


2017年05月09日(火) [長年日記]

[howto][dev] GNU makeでMakefileのあるディレクトリのパスを得る

Makefileが配置されているディレクトリのパスを得るにはどうすればよいのか。

GNU makeにはCURDIRという変数があって、makeの -C オプションが使われているときにはこれを使えるけれど、makeの -f オプションで異なるディレクトリのMakefileを読み込んだ時にはうまくいかない(CURDIRの値はカレントディレクトリなので)。

検索すると、How to get a Makefile directory pathという記事で

$(dir $(realpath $(firstword $(MAKEFILE_LIST))))

を使うという方法が紹介されていた。MAKEFILE_LISTという変数にはMakefileとそこからincludeされたファイルの名前が設定されているようだ。


2017年05月18日(木) [長年日記]

[unix][shell] 文字の色を変えるスクリプト(2)

11年前にメモした文字の色を変えるスクリプトを使いたくなった。

ここに書いていた「2回使うと何も表示されない」という問題は、sedに -u オプションを付ければ解決する模様。また、文字の色を変えるより背景色を変えた方が目立つので、

#!/bin/bash
if [ $# -lt 1 ]; then
    echo 'usage $0 <string>'
    exit 1
fi

sed -u -e "s/${1}/^[[43m${1}^[[0m/g"

と微修正(「^[」の部分はエスケープ文字を直接入力する必要がある)。

あと、このスクリプトは指定文字列以外に「${1}」という文字列の色も変えてしまうことに気付いた。なんでこういう動作になるのかな。


2017年05月19日(金) [長年日記]

[python] Pythonでメールのヘッダフィールドをエンコードする

メールのヘッダフィールドに日本語を含めるにはRFC 2047に従ってエンコードする必要があるようである。Pythonではこのエンコード処理をemail.headerモジュールで行うことができる。

% python
Python 2.7.5 (default, Nov  6 2016, 00:28:07)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import email.header
>>> print email.header.Header(u"あ" * 100, "iso-2022-jp", header_name="Subject")
=?iso-2022-jp?b?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=
 =?iso-2022-jp?b?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=
 =?iso-2022-jp?b?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=
 =?iso-2022-jp?b?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=
 =?iso-2022-jp?b?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=
 =?iso-2022-jp?b?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIbKEI=?=

このように、Headerオブジェクトを生成すればエンコードができる。header_nameは指定しなくても問題ないが、指定するとエンコード後の最初の行の文字数をヘッダ名の分(上の例だと「Subject:」)だけ減らしてくれる。

上の例ではcharsetとして"iso-2022-jp"を指定しているが、この場合もしエンコード対象文字列にiso-2022-jpでエンコードできない文字が含まれていると自動的にutf-8でエンコードしてくれて気が利いている。

>>> print email.header.Header(u"①" * 100, "iso-2022-jp", header_name="Subject")
=?utf-8?b?4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg?=
 =?utf-8?b?4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg?=
 =?utf-8?b?4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg?=
 =?utf-8?b?4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg?=
 =?utf-8?b?4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg?=
 =?utf-8?b?4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg?=
 =?utf-8?b?4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg4pGg?=

2017年05月24日(水) [長年日記]

[shell][unix] Bashにおけるリダイレクト

おれはリダイレクトを雰囲気で使っている。たまに混乱するので気になる所を調べてメモしておく。使用するシェルはBashとする。

ファイルディスクリプタ

リダイレクトを理解するにはファイルディスクリプタを理解する必要があるだろう。ファイルディスクリプタをどう表現するのが正しいかよくわからないが、

  • 入出力先を持つ非負の整数

でどうだろうか。入出力先はファイルや画面、キーボードなどになる。

プロセスは生成されたときに0,1,2という3つのファイルディスクリプタを持っている。これらが入出力先として何を持っているのかは親プロセスが決めることであり、プロセス自身は関知しない。

リダイレクトとは

シェルがリダイレクトで行うのは、

  • 新しいファイルディスクリプタを作成しその入出力先を設定する。
  • 既存のファイルディスクリプタの入出力先を変更する。
  • ファイルディスクリプタを破棄する。

だと理解すればいいと思う。

大事な点として、リダイレクトによるファイルディスクリプタの操作は対象とするコマンドに対してのみ行われ、コマンドを実行するシェル自体には影響がないということがある。例えば

$ echo hello >/tmp/oreore

はファイルディスクリプタ1の出力先を変更するが、この変更はechoコマンドのみに作用し、この後に実行するコマンドには影響しない。例外はコマンドが「exec」だった場合で、それについては別にメモする(メモした)。

Bashのリダイレクトの記法はたくさんあるが、以下ではその中のいくつかについてだけメモする。

[n]>word

よく使う、ファイルへのリダイレクトである。[n]にはファイルディスクリプタ(省略すると1になる)、wordにはファイルパスを指定し、これにより次が行われる。

  • ファイルwordがないなら作成する。
  • ファイルディスクリプタ[n]がないなら作成する。
  • ファイルディスクリプタ[n]の出力先としてファイルwordを設定する。

例えば、

$ echo hello 5>/tmp/oreore

を実行すると空の /tmp/oreore ファイルが作られる。echoコマンドはファイルディスクリプタ1へ出力を行うコマンドであり、ファイルディスクリプタ5へは出力を行わないからである。

[n]>&word

>の後ろに&を付けると、wordにはファイルパスではなくファイルディスクリプタか - を指定することになる。wordがファイルディスクリプタの場合は次が行われる。

  • ファイルディスクリプタ[n]がないなら作成する。
  • ファイルディスクリプタ[n]の出力先として「ファイルディスクリプタwordの出力先」を設定する。

wordが - の場合は次が行われる。

  • ファイルディスクリプタ[n]を破棄する。

例えば、

$ echo hello 5>/tmp/oreore >&5

とするとファイルディスクリプタ1の入出力先が /tmp/oreore になるので /tmp/oreore には hello が出力される。あるいは、サブシェルを使って

$ (echo hello >&5) 5>/tmp/oreore

としても同じ結果になる。また、

$ echo hello >&-

とするとファイルディスクリプタ1が破棄されるのでechoコマンドはエラーになる。

[n]>&digit-

似た記法があって、>& の後にファイルディスクリプタdigitと - の両方を指定した場合は次が行われる。

  • ファイルディスクリプタ[n]がないなら作成する。
  • ファイルディスクリプタ[n]の出力先として「ファイルディスクリプタdigitの出力先」を設定する。
  • ファイルディスクリプタdigitを破棄する。

ファイルディスクリプタdigitをもう使わない場合はこちらの記法の方がいいのだろうけど、あまり使われない気がする。

ここまでの3種類の記法は > を < に変えるとファイルディスクリプタへ出力先ではなく入力先を設定することになる。出力のリダイレクトにはもう一つ記法があるのでそれもメモしておく。

&>word, >&word

wordにはファイルパスを指定する。これにより次が行われる。

  • ファイルwordがないなら作成する。
  • ファイルディスクリプタ1または2がないなら作成する。
  • ファイルディスクリプタ1と2の出力先としてファイルwordを設定する。

この記法はファイルディスクリプタ1と2の両方をファイルにリダイレクトするときに使う。


2017年05月25日(木) [長年日記]

[shell][unix] Bashのexecコマンド

Bashにはexecという組み込みコマンドがある。このコマンドには

command が指定されていない場合、任意のリダイレクトはカレントシェルで効果を持ち、 終了ステータスは 0 となります。

という機能がある。これはシェルが持つファイルディスクリプタを操作する目的に使える。例えば、

$ exec >/tmp/oreore

を実行すると、ファイルディスクリプタ1(標準出力)の出力先が /tmp/oreore に変更され、以降このシェル上での標準出力への出力は画面ではなく /tmp/oreore に対して行われるようになる。

(ファイルディスクリプタやリダイレクトについては昨日のメモを参照せよ)


2017年05月29日(月) [長年日記]

[shell][unix] flockコマンドを使用して二重起動を防ぐ

Linux上でコマンドの二重起動を防ぐにはどうするか。flockコマンドを使うのがよさそう。

flockコマンドは指定ファイルをロックしてコマンドを実行するコマンド。-n オプションによりロック中の場合は直ちに処理を終了できる。

$ flock -n lock echo hello

flockにはファイルではなくファイルディスクリプタを指定することもできて、この場合はそのファイルディスクリプタを閉じるまでロックが維持される。シェルスクリプトの中で排他制御を行うときはこのモードを使とよい。シェルスクリプト自身を入力用にオープンしてロックするのがお手軽。

#!/bin/bash
set -e

(
    flock -n ${my_fd} || exit 1
    sleep 5
    echo hello
) {my_fd}< "$0"

echo world

ここで、「{my_fd}<」というのは使用するファイルディスクリプタ番号を自動で決めるための記法。Bashのマニュアルのリダイレクトの節に説明がある。

二重起動防止の用途ならサブシェルを使わない方が分かりやすいかな。

#!/bin/bash
set -e
exec {my_fd}< "$0"
flock -n ${my_fd}
sleep 5
echo hello
echo world

参考


2017年05月31日(水) [長年日記]

[shell] Bashで配列

Bashでは

a=(a b c d e)

のように()を使うと配列を生成することができる。

要素の参照

echo ${a[0]}      #=> a
echo ${a[1]}      #=> b
echo ${a[-1]}     #=> e
echo ${a[@]}      #=> a b c d e
echo ${a[@]:2}    #=> c d e
echo ${a[@]:2:2}  #=> c d

要素数とインデックスの参照

echo ${#a[@]}     #=> 5
echo ${!a[@]}     #=> 0 1 2 3 4

要素の追加と削除

a+=(hello)   && echo ${a[@]}  #=> a b c d e hello
a[100]=world && echo ${a[@]}  #=> a b c d e hello world
unset a[2]   && echo ${a[@]}  #=> a b d e hello world
echo ${!a[@]}                 #=> 0 1 3 4 5 100

[shell] Bashで計算

Bashは算術式展開を使うと簡単な計算ができる。

echo $((1 + 1))           #=> 2
echo $((2 ** 3))          #=> 8
i=10 && echo $((i % 4))   #=> 2

[shell] Bashで乱数

Bashのシェル変数にはRANDOMという変数があり、これを参照すると0~32767のランダムな整数を得られる。

$ for n in {1..10}; do echo $RANDOM; done
12349
4092
31967
6007
10451
27446
25349
2318
12177
23783