pikesaku’s blog

個人的な勉強メモです。記載内容について一切の責任は持ちません。

Mailmanメモ

user_optionsの値について

各項目の説明(ヘルプ文言抜粋)

項目 説明
制限 個人単位で会員に制限(モデレーション)をかけるフラグ
このフラグをセットすると,この会員の投稿は制限されます
セットしなければ自由に投稿できます
隠れ会員 この会員のアドレスは 会員名簿では隠しますか?
配送停止 この会員への配送は停止されていますか?
もし, そうなら 以下の略号で配送を停止した理由が示されます
U->会員自身が停止
A→管理者が停止
B→エラーメール過剰で停止
?→理由不明
受領 この会員からの投稿に確認通知を送りますか?
控え無 この会員からの投稿は本人には配送しないようにしますか?
ダブリ無 この会員には同じメールを重複して配送 しないようにしますか?
まとめ読み この会員へは「まとめ読み」で配送しますか?
設定しなければ, 1本ずつ配送される普通配送になります
平文 「まとめ読み」の場合, この会員へは平文で配送しますか?
でなければ, MIME 添付を使います
言語 会員が選んだ言語



設定毎のuser_options値

制限 隠れ
会員
配送
停止
受領 控え無 ダブリ
まとめ
読み
平文 言語
- - - - - - - - - 設定なし
on - - - - - - - - 128
on on - - - - - - - 144
on on on - - - - - - 144
on on on on - - - - - 148
on on on on on - - - - 150
on on on on on on - - - 406
on on on on on on on - - 406
on on on on on on on on - 414
配送停止をonにした時は以下設定がされる。

'delivery_status': { 'メンバーアドレス': (3, 1482648090.696833),}
値のタプル2個目の要素はunixタイム

まとめ読みをonにした時は以下設定がされる。

'digest_members': { 'メンバーアドレス': 0},

デフォルトは、ダブリ無と平文がonで264

ダブリ無で256加算
平文が8加算

制限付き会員から投稿があったときの動作(member_moderation_action)

各設定時のmember_moderation_actionの値

保留 0
拒否 1
破棄 2



FML→Mailman投稿制御設定対応表

FML設定 Mailman設定
フィルタ設定
(PERMIT_POST_FROM)
メンバー登録 フィルタ設定
(generic_nonmember_action)
(member_moderation_action)
メンバー登録
anyone actives
members
両方
承認(0)
保留(0)
-
members
のみ
配送停止
actives
のみ
-
members_only actives
members
両方
承認(0)
破棄(2)
-
members
のみ
配送停止
actives
のみ
制限
moderator actives
members
両方
承認(0)
保留(0)
-
members
のみ
配送停止
actives
のみ
-

FMLモデレータ動作

moderatorsファイルがない場合は、$MAINTAINER定義アドレス(ML名-admin)にフォワードされる。

activesのフォーマット

NONE TITLE 8.html
NONE TITLE 2.html

ポイント

①activesは以下フォーマット
アドレス オプション # コメント
②コメントは単なるコメント
③アドレス移行の部分はオプションとして解釈される。
④オプションは2パターン記述方法があり。
V1 フォーマットは
数字(フォーマット) まとめおくりの指定
数字以外 リレーサーバ

V2 フォーマットでは将来の拡張のため
m=まとめ送り指定
r=リレーサーバ
s=1(skip を意味する)
 ※m=5と5は同じということ。この5は時間。
⑤V1記述は使われないとの記述もあり。V2のみかも。
⑥まとめ読みは、cronで1時間に一度プログラムが実行され、activesファイルをチェックし動作が決定される。

skipとoffの違い
http://www.fml.org/fml/Japanese/how_to_subscribe/8.html

skipは管理者、offはユーザー?

動きメモ

①メンバーが-ctl宛に本文にoffと書いてメール送信
②membersファイルは変更無し。activesファイルは以下のように変わる。
変更前

test1@example.com 
test2@example.com 
test3@example.com 

変更後

----- Backup on 17/01/01 12:56:30 -----
test1@example.com 
test2@example.com 
test3@example.com 
[root@ip-172-31-40-69 conv_fml2mm]# cat /var/spool/ml//fml1/actives
#.FML HEADER
# NEW FORMAT FOR FURTHER EXTENSION
# e.g. fukachan@phys r=relayserver m=3u s=skip 
# r= relayserver
# m= matomeokuri parameter is time and option
# s= skip. can post from this address but not delivered here.
#
# the same obsolete format is compatible with new format and as follows:
# e.g. fukachan@phys relayserver matome 3u
#.endFML HEADER
#	test1@example.com  
test2@example.com 
test3@example.com 

③actives.bakのバックアップができる。
④コマンドメール送信したユーザーには、以下メールが送信される。

off
OFF [test1@example.com]  accepted.

--fml1@example.com, Be Seeing You!    

************************************************************

      Help: <mailto:fml1-ctl@example.com?body=help>
Unsubscribe: <mailto:fml1-ctl@example.com?body=unsubscribe>

If you have any questions or problems,
  please contact fml1-admin@example.com
      or 
  send e-mail with the body "help"(without quotes) to
     fml1-ctl@example.com
     (here is the automatic reply, so more preferable)

e.g. on a Unix Machine
(shell prompt)% echo "help" |Mail fml1-ctl@example.com

************************************************************

⑤MLに投稿しても配信はされない。
⑥onのメールを送信すると再度有効化される。
 ちなみに手作業でactivesファイルを編集し、コメントを1つだけ付けて、スペースなしの場合(#test1@example.com)でも、onで有効化される。
⑦送信者にはコマンド成功した通知メールがくる。

activesの第一フィールドのみチェックすればOK

memberファイルのフォーマットは?

以下記述があり。

なおメンバーファイルとしては各行のアドレスより後ろの部分は何にも使われていませんので、勝手に使って構いません。
しかしながら、自動登録の場合は $MEMBER_LIST と $ACTIVE_LIST は同じもの($MEMBER_LIST)が使われます。よってそのフォーマットは $ACTIVE_LIST 形
式であると仮定する必要があります。

自動登録機能が有効な場合は、同じフォーマットになる可能性があるが、未使用の場合が、第一フィールドのみが有効パラメタ

シェルを学ぶ

参考URLのシェルスクリプトがかっこいいので勉強
シェルのテクニック満載で、これでもかっ!ってくらいあった気がする。大変勉強になりました。

要チェックキーワード

FIXMEは修正を要する部分

変数を${VAR}と$VARで定義する場合の違い

参照時に変数名部分を明示的にする
変数を使用する | UNIX & Linux コマンド・シェルスクリプト リファレンス
 

# cat ./a.sh
#!/bin/ksh

A="1"
B="2"
echo $AB
echo ${A}B
echo ${A}${B}
# sh ./a.sh

1B
12



環境変数 LC_ALL

ロケールに関する環境変数の一括上書き

# echo $LANG
ja_JP.utf8
# ls /tmp/noexist                                                                            
ls: /tmp/noexist にアクセスできません: そのようなファイルやディレクトリはありません
# export LC_ALL=C
# echo $LANG
ja_JP.utf8
# ls /tmp/noexist
ls: cannot access /tmp/noexist: No such file or directory
# 



シェルで未定義変数を参照した時にエラー終了させる

# cat /tmp/a.sh 
#!/bin/bash

set -u
echo $not_defined
echo test
# sh /tmp/a.sh 
/tmp/a.sh: 行 4: not_defined: 未割り当ての変数です
# 

 

setコマンドはshellにオプションを設定するコマンド

${VAR+set}の意味

変数の定義状況の確認で使う
この"set"は他の文字列でもOK。変数定義状況確認を示す為、setを使っているだけ

以下URLによると
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02
 
${VAR+hoge}の場合

VAR変数定義あり かつ NULLでない場合
→${VAR+hoge}はhoge

VAR変数定義あり かつ NULLである場合
→${VAR+hoge}はhoge

VAR変数定義なし
→${VAR+hoge}はNULL

では確認

# cat ./a.sh 
#!/bin/bash

A="test"
echo ${A+hoge}
A=""
echo ${A+hoge}
echo ${B+hoge}
# sh ./a.sh
hoge
hoge

 


ちなみに、
${VAR:+word}の場合
VARにNULL以外の値がセットされているかの判定に使える。

# cat ./a.sh 
#!/bin/bash

A="test"
echo ${A:+hoge}
A=""
echo ${A:+hoge}
echo ${B:+hoge}
# sh ./a.sh
hoge



expressionを囲う角括弧が1個の場合と2個の場合の違いは

※[]と[[]]の違い

man bashでは、、、
expression の説明に以下記述があり。

Word splitting and pathname expansion are not performed on the words between the and ; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and quote removal are performed.

すごく詳しい解説があった!
https://fumiyas.github.io/2013/12/15/test.sh-advent-calendar.html
 

①ワード分割とパス名展開がされない

例)

# [ -f a ] && echo Exist
Exist
# [ -f a* ] && echo Exist
-bash: [: too many arguments
# [[ -f a ]] && echo Exist
Exist
# [[ -f a* ]] && echo Exist
# [[ -f a? ]] && echo Exist
# A="hoge hoge"
# [ -f $A ] && echo Exist
-bash: [: hoge: binary operator expected
# [ -f "$A" ] && echo Exist
# [[ -f $A ]] && echo Exist
# 

 


②数値の比較演算子では左右の値が算術式展開される

例)

# [ 1+2 -eq 3 ] && echo "Ugoku"
-bash: [: 1+2: integer expression expected
# [[ 1+2 -eq 2+1 ]] && echo "Ugoku"
Ugoku
# [[ '1 + 2' -eq '2 + 1' ]] && echo "Ugoku"
Ugoku

 

ポイントは、式に空白を含める場合は、クオートする必要がある点

③文字列の比較演算子 == で右辺がクオートされてない場合、完全一致ではなくパターンマッチになる点

例)

# [ 'test' == tes* ] && echo Match
# [[ 'test' == tes* ]] && echo Match
Match
# [[ 'test' == "tes*" ]] && echo Match
# 

 


④[]にはない文字列の比較演算子を使える。

例)
正規表現が可能。こりゃ便利

# [ "test" =~ ^[a-z]{4}$ ] && echo Match
-bash: [: =~: binary operator expected
# [[ "test" =~ ^[a-z]{4}$ ]] && echo Match
Match

 <や>で文字列順番の比較が可能

# [[ "abc" < "def" ]] && echo Match
Match
# [[ "abc" > "def" ]] && echo Match
# 

 


ちなみに[]でやると、<>はリダイレクトが動いてしまい、こんな動きになる。

# [ "abc" > "def" ] && echo "Match"
Match
# ls ./def 
./def
# [ "abc" < "def" ] && echo "Match"
Match
# ls ./abc ./def                                                                                                                                                        
ls: cannot access ./abc: No such file or directory
./def
# rm -f ./def 
# [ "abc" < "def" ] && echo "Match"
-bash: def: No such file or directory
# 

 


⑤条件式の論理演算子(and・or)の記述が異なる。は-a,-o、[]は&&、||

[]の場合

# [ 1 -eq 1 -a 2 -eq 2 ] && echo Match
Match
# [ 1 -eq 1 -o 2 -eq 2 ] && echo Match
Match
# [ 1 -eq 1 && 2 -eq 2 ] && echo Match
-bash: [: missing `]'
# [ 1 -eq 1 || 2 -eq 2 ] && echo Match
-bash: [: missing `]'
-bash: 2: command not found

 


[[]]の場合

# [[ 1 -eq 1 && 2 -eq 2 ]] && echo Match
Match
# [[ 1 -eq 1 || 2 -eq 2 ]] && echo Match
Match
# [[ 1 -eq 1 -a 2 -eq 2 ]] && echo Match
-bash: syntax error in conditional expression
-bash: syntax error near `-a'
# [[ 1 -eq 1 -o 2 -eq 2 ]] && echo Match
-bash: syntax error in conditional expression
-bash: syntax error near `-o'
# 

結論: これからは、上記を踏まえ角括弧2個を使う!正規表現が使えるのがいい!


function pdieのexit ${2-1}の意味について

function pdie {
  perr "$1"
  exit ${2-1}
}

 


http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02
${VAR-hoge}は上記表では以下の様に展開される。

VAR変数定義あり かつ NULLでない場合
→${VAR-hoge}はVARの値

VAR変数定義あり かつ NULLである場合
→${VAR-hoge}はNULL

VAR変数定義なし
→${VAR-hoge}はhoge

終了ステータスコードをpdie呼び出し時の第二引数で指定できる

簡易動作確認コード

# cat ./a.sh 
#!/bin/ksh

function hoge {
  echo ${2-1}
}

hoge
hoge ""
hoge "" ""
hoge "a" ""
hoge "a" "b"
# sh ./a.sh 
1
1


b



[[ -t 0 ]]の意味について

ファイルデスクリプタ0(標準入力)があるかを確認
以下をターミナル・cronで、それぞれ実行すると結果は異なる。
[[ -t 0 ]]; echo $?
ターミナル経由の場合は0(成立)
cron経由の場合は1(非成立)

以下でやっていることは、、、

function run {
  pinfo "Run command: $*" 1>&2
  if [[ -n ${NO_RUN+set} ]]; then
    [[ -t 0 ]] || cat >/dev/null
  else
    "$@"
  fi
}

 


パイプとファイルディスクリプタの仕組み理解できていないが動作確認した結果では、、、
・run関数がパイプ経由で呼び出された場合、[[ -t 0 ]]は成立しない
・run関数がパイプ経由で呼び出されていない場合、[[ -t 0 ]]は成立する

このあたりの動作は以下URLの情報が関係あるのかな。。。
http://blog.livedoor.jp/cielo_cielo/archives/65111675.html

ひとまず上記より以下の事が判明
NO_RUN環境変数の定義がある場合
・パイプで呼び出された場合、標準入力は/dev/null行き
・パイプで呼び出されてない場合、何も実行されない
→要は何も実行されないし、スクリプト実行したコンソールには何も出力されない。

NO_RUN環境変数の定義がない場合
"$@"が実行される。"$@"は関数の引数全て。このスクリプトではrun関数にはコマンドが引数に指定されている。

・パイプで呼び出された場合、標準入力はrun関数の引数に指定されたteeコマンドの標準入力になる。
・パイプで呼び出されてない場合、引数で指定されたコマンドが実行される。

run関数で色々なコマンドを実行する。コマンド自体に引数を指定する場合、パイプ経由で呼び出す。


[[ ${1-} = @(|0) ]]の意味について

以下のコードがあり

function fml_true_p {
  [[ ${1-} = @(|0) ]] && return 1
  return 0
}

 


まず、"="について。"=="じゃないの?
man bashを見ると、testコマンドの文字列比較では、==だけでなく=も使えて、POSIX準拠するには=の方がよいと記載があり。

string1 == string2
string1 = string2
True if the strings are equal. = should be used with the test command for POSIX conformance.<<

次に${1-}について

${1-}は、以下の様に展開される。
①第一引数がある場合、第一引数の値
②第一引数があるが空文字の場合、空文字
③第一引数がない場合、②と同じ(=空文字)

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02
${1-}は上記URLのwordが省略された場合

最後に@(|0)について

@(|0)は、man bashによるとシェルのワイルドカードマッチの記述で、man bashGoogle翻訳によると

 いくつかの拡張パターンマッチング演算子が認識されます。以下の説明では、パターンリストは、|で区切られた1つ以上のパターンのリストである。複合パターンは、以下のサブパターンのうちの1つ以上を使用して形成することができる。

 ?(パターンリスト)
 与えられたパターンの0回または1回のオカレンスと一致します。
 *(パターンリスト)
 与えられたパターンの0回以上の出現にマッチします。
 +(パターンリスト)
 与えられたパターンの1つ以上の出現と一致します。
 @(パターンリスト)
 指定されたパターンの1つに一致します。

少し分かりづらい。動作確認したところ以下の通り

①?(PTRN_LIST)の場合

# [[ 1 = ?(1|2|3) ]] && echo  yes
yes
# [[ 0 = ?(1|2|3) ]] && echo  yes
# [[ 10 = ?(1|2|3) ]] && echo  yes
# [[ 13 = ?(1|2|3) ]] && echo  yes
# 

 


②*(PTRN_LIST)の場合

# [[ 1 = *(1|2|3) ]] && echo  yes
yes
# [[ 0 = *(1|2|3) ]] && echo  yes
# [[ 10 = *(1|2|3) ]] && echo  yes
# [[ 13 = *(1|2|3) ]] && echo  yes
yes
# 

 


③+(PTRN_LIST)の場合

# [[ 1 = +(1|2|3) ]] && echo  yes
yes
# [[ 0 = +(1|2|3) ]] && echo  yes
# [[ 10 = +(1|2|3) ]] && echo  yes
# [[ 13 = +(1|2|3) ]] && echo  yes
yes
# 

 


④@(PTRN_LIST)の場合

# [[ 1 = @(1|2|3) ]] && echo  yes
yes
# [[ 0 = @(1|2|3) ]] && echo  yes
# [[ 10 = @(1|2|3) ]] && echo  yes
# [[ 13 = @(1|2|3) ]] && echo  yes
# 

 


上記よりパターン定義した文字列(上記の場合1,2,3)が、何回展開されるかの違い。?は0or1回。@(1|2|3)を展開しても、1文字にしかならない。その為、10や13はマッチしない。*は0回以上、+は1回以上だから13がマッチする。@は1回だけなので13はマッチしない。

補足として以下動作を見るとより分かりやすい。

# [[ 0 = ?(1|2|3)0 ]] && echo  yes
yes
# [[ 0 = *(1|2|3)0 ]] && echo  yes
yes
# [[ 0 = +(1|2|3)0 ]] && echo  yes
# [[ 0 = @(1|2|3)0 ]] && echo  yes
# 

 


まあ、manに書いてある通り。

fml_true_p関数の動きは、以下の通り

第一引数がない時 return 1
第一引数が空文字の時 return 1
第一引数が上記以外の時 return 0

 

${0##*/}の意味について

以下のコードがあり

tmp_dir=$(mktemp -d /tmp/${0##*/}.XXXXXXXX) || pdie "Cannot create temporary directory"

 


これは、ここにも記載した内容
pikesaku.hatenablog.com

要は変数の値に対しパターンを指定して、部分的に切り出す操作。
${0##*/}は、${0}がスクリプト名に展開されるので、スクリプト名から最長一致で/が取り除かれるので、ファイル名だけに展開される。

例)

# cat ./a.sh
#!/bin/bash

echo '${0}     -> ' ${0}
echo '${0##*/} -> ' ${0##*/} 
# sh ./a.sh
${0}     ->  ./a.sh
${0##*/} ->  a.sh
# 

 

mktempコマンド

テンポラリファイルのファイル名を生成するコマンド。

# mktemp /tmp/a.XXXXX
/tmp/a.591zh

引数はテンプレート。X部分はランダム文字列が生成されて割り当てられる。

trapコマンド

以下URLが分かりやすい
シグナルと trap コマンド | UNIX & Linux コマンド・シェルスクリプト リファレンス

シグナルハンドラ。シェルにもあったんだ。。。。

trap 'コマンド' シグナルリスト
例) # trap 'echo finish;exit' INT HUP

${MAILMAN_USER-mailman}の意味。

${VAR-word}。環境変数があったら、そっちを使う。なければデフォルトのmailman使う。

よくよく考えると、このスクリプトはコードを変更しなくても、色々な環境で使えるように考えてくれているんだな。
きめ細かい心遣いに感謝。

shiftコマンド

引数を1つづらす

# cat /tmp/a.sh 
#!/bin/bash

for i in $(seq 1 3)
do
  echo $1; shift
done
# sh /tmp/a.sh a b
a
b

 

mm_url_host="${1-}"; ${1+shift}の意味

引数があったら、その値を変数に入れて、更にshiftを実行する。

# cat /tmp/a.sh                                                                              
#!/bin/bash

a=$1; shift
b=$1; shift
c=${1-};
${1+shift}
d=$1
echo "a is $a"
echo "b is $b"
echo "c is $c"
echo "d is $d"
# sh /tmp/a.sh 1 2 3 4 5                                                                     
a is 1
b is 2
c is 3
d is 4

 

typesetコマンド

色々機能があるようだけど、このスクリプトでは以下オプションを使っている。

・-lオプション
変数の値を小文字にする

# cat /tmp/a.sh 
#!/bin/bash

a="A"
echo $a
typeset -l a
a="A"
echo $a
# sh /tmp/a.sh 
A
a

 


・-uオプション
変数の値を大文字にする

・-Aオプション
associative array(連想配列)を宣言する。

# cat /tmp/a.sh 
#!/bin/bash

typeset -A aa
aa["id"]="bb"
echo ${aa["id"]}
# sh /tmp/a.sh 
bb

 


※展開時は、{}で囲う必要あり。囲わないと展開されない。(RHEL7 bash 4.2.46)

read -rについて

・-rオプションでバックスラッシュを、エスケープ文字として扱わなくなる。

# cat /tmp/a.sh 
#!/bin/bash

echo "# no defined -r"
(echo "a";echo 'b\';echo "c") \
| while read a
do
  echo $a
done

echo "# defined -r"
(echo "a";echo 'b\';echo "c") \
| while read -r a
do
  echo $a
done
# sh /tmp/a.sh 
# no defined -r
a
bc # b\の\が評価された為、echoコマンドが付与する改行コードが無効化され1行になる
# defined -r
a
b\
c

 

以下のsed正規表現の意味

sed \
  -n \
  -e 's/^\$\([A-Za-z][A-Za-z_]*\)[ 	]*=[ 	]*\(.*\);[ 	]*$/\1 \2/p' \
  -e 's/^[ 	]*&*DEFINE_FIELD_FORCED(.\([^"'"'"']*\).[ 	]*,[ 	]*\([^)]*\).*$/\1 \2/p' \
  config.ph \

 


・-nは出力抑制
・-eはスクリプト実行

まず以下の正規表現について

-e 's/^\$\([A-Za-z][A-Za-z_]*\)[      ]*=[    ]*\(.*\);[      ]*$/\1 \2/p'

AAA = "aaa";
上記だと、AAAと"aaa"を出力する。※[]はページ表示されうとスペース1文字に見えるが、ソースコードではスペースとタブ。

次に以下の正規表現について

-e 's/^[      ]*&*DEFINE_FIELD_FORCED(.\([^"'"'"']*\).[       ]*,[    ]*\([^)]*\).*$/\1 \2/p'

 


以下のパターン記述の意味は?

[^"'"'"']

 


上記はダブルクオート・シングルクオートではない文字がマッチする。
sedスクリプトがシングルクオートを囲まれている場合に、スクリプト内でシングルクオートを使うには、上記の記述が必要。

^の後の文字列は以下に分解されて解釈されている。

"

→ダブルクオート1文字の意味

'"'"'

 


→シングルクオート1文字の意味。シェルはシングルクオートで括られた上記をシングルクオート1つに展開する。

例)

# echo ''"'"''                                                                                                                                                          
'
# 

 


以下URLが根拠かと思われる。こんな展開ができるとは。。。

bash - How to escape single-quotes within single-quoted strings? - Stack Overflow
http://sed.sourceforge.net/grabbag/tutorials/sedfaq.txt
quoting - How to echo `single quote` when using single quote to wrap special characters in shell? - Unix & Linux Stack Exchange

シングルクオート内でシングルクオートをリテラルとして使いたい時のテクニック

例)

# echo '"'"'
> ^C →Ctrl+Cで中断。これだと、2個目のダブルクオートが閉じてない為、コマンドが実行されない。
# echo '\''                                                                                                                                    
> ^C →Ctrl+Cで中断。これだと、3個目のシングルクオートが閉じていない為、コマンド実行がされない。シングルクオート内ではバックスラッシュもリテラル扱いになる。
# echo ''"'"''
'

 


で、結局この正規表現

  -e 's/^[ 	]*&*DEFINE_FIELD_FORCED(.\([^"'"'"']*\).[ 	]*,[ 	]*\([^)]*\).*$/\1 \2/p' \
&DEFINE_FIELD_FORCED("reply-to", "$From_address, $MAIL_LIST");

 


上記だと、reply-toと"$From_address, $MAIL_LIST"が出力される。
例)

# echo  '&DEFINE_FIELD_FORCED("reply-to", "$From_address, $MAIL_LIST");' | sed -n -e 's/^[ ]*&*DEFINE_FIELD_FORCED(.\([^"'"'"']*\).[ ]*,[ ]*\([^)]*\).*$/\1/p'
reply-to
# echo  '&DEFINE_FIELD_FORCED("reply-to", "$From_address, $MAIL_LIST");' | sed -n -e 's/^[ ]*&*DEFINE_FIELD_FORCED(.\([^"'"'"']*\).[ ]*,[ ]*\([^)]*\).*$/\2/p'
"$From_address, $MAIL_LIST"
# 

 

cf_value="${cf_value//\\@/@}"の意味

man kshGoogle翻訳では

パラメータを展開し、パターンの最長一致を指定された文字列に置き換えます。文字列中の\ nのそれぞれの出現は、n番目のサブパターンと一致するパラメータの部分に置き換えられます。最初の形式では、patternの最初のオカレンスのみが置き換えられます。 2番目の形式では、patternの各一致が指定された文字列に置き換えられます。 3番目の形式はパターンマッチを文字列の先頭に限定し、4番目の形式はパターンマッチを文字列の末尾に限定します。 stringがヌルの場合、パターンは削除され、文字列の/ inは省略されます。パラメータが@、*、添字@または*を持つ配列変数の場合、各要素に代入演算が交互に適用されます。この場合、単語の文字列部分は各要素について再評価されます。

全体的に分からないうえに、'/'と'//'の違いが分からない。。。。
変数の値に対しパターンマッチした部分を置き換えるよう。
動作確認した結果は以下の通り。

# A='ababab'
# B=${A//ab/AB}
# echo $B
ABABAB
# A='ababab'
# B=${A/ab/AB}
# echo $B
ABabab

 


'/'の場合は最初にマッチした部分だけ置換、'//'は全部!
やっている事は、\@を@へ置き換え。

fml_cf[$cf_name]="${cf_value/\$DOMAINNAME/${fml_cf[DOMAINNAME]-}}"の意味

"${fml_cf[DOMAINNAME]-}"の-は、${A-}と同じ。
fml_cf[DOMAINNAME]が定義済みであれば、その値、未定義or空であれば空文字に置換される。

mm_postid=$(cat seq 2>/dev/null) && let mm_postid++の意味

letは算術展開する。動作は以下の通り。

# let "A=2*3"
# echo $A
6
# let "A++"
# echo $A
7
# let "A+1"
# echo $A
7
# let "A=A+1"
# echo $A
8
# 

 

mm_subject_post_id_fmt="%0${fml_cf[SUBJECT_FORM_LONG_ID]-5}d"の意味

これは、以下の動き

"${fml_cf[SUBJECT_FORM_LONG_ID]"が空文字でなければ、その値。空文字なら5

mm_subject_prefix="${fml_cf[SUBJECT_TAG_TYPE]/ /${fml_cf[BRACKET]} $mm_subject_post_id_fmt}"の意味

これは、以下の動き

# A='(:)'
# B="${A/ /test hoge}"
# echo $B
(:)
# A='(:) '
# B="${A/ /test hoge}"
# echo $B
(:)test hoge
# 

 

以下sedの意味

sed -n \
      -e '1 {h; $ !d}' \
      -e '$ {x; s/\n / /g; p}' \
      -e '/^ / {H; d}' \
      -e '/^ /! {x; s/\n / /g; p}' \

 


参考
Unix Sed Tutorial : 7 Examples for Sed Hold and Pattern Buffer Operations
 

スクリプトの処理内容は以下の通り

1個目のスクリプトの処理
-e '1 {h; $ !d}' \

 


1行目であれば以下の処理をする。
・パターンスペースをホールドスペースにコピー
・最終行でなければパターンスペースを削除

2個目のスクリプトの処理
-e '$ {x; s/\n / /g; p}' \

 


最終行であれば以下の処理をする。
・パターンスペースとホールドスペースを入れ替える。
・改行+スペースをスペースに置換(?)
・パターンスペースを出力

3個目のスクリプトの処理
-e '/^ / {H; d}' \

 


スペースで始まる行の場合、以下の処理をする。
・パターンスペースをホールドスペースに追記
・パターンスペースを削除

4個目のスクリプトの処理
-e '/^ /! {x; s/\n / /g; p}' \

 


スペースで始まらない行の場合、以下の処理をする。
・パターンスペースとホールドスペースを入れ替える。
・改行+スペースをスペースに置換(?)
・パターンスペースを出力

上記の動作を踏まえ、以下の実行結果をトレースしてみる。

# cat ./a
hoge1: inu
hoge2: neko
 kuma
hoge3: saru
# sed -n -e '1 {h; $ !d}' -e '$ {x; s/\n / /g; p}' -e '/^ / {H; d}' -e '/^ /! {x; s/\n / /g; p}' ./d
hoge1: inu
hoge2: neko kuma
hoge3: saru# 

 

①1行目処理

1行目データがパターンスペースに読み込まれる

パターンスペース hoge1: inu
ホールドスペース -

1個目のスクリプト(-e '1 {h; $ !d}')の処理
パターンスペースの内容がホールドスペースにコピーされる。

パターンスペース hoge1: inu
ホールドスペース hoge1: inu

パターンスペースの内容が削除される。

パターンスペース -
ホールドスペース hoge1: inu

2個目のスクリプト(-e '$ {x; s/\n / /g; p}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース -
ホールドスペース hoge1: inu

3個目のスクリプト(-e '/^ / {H; d}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース -
ホールドスペース hoge1: inu

4個目のスクリプト(-e '/^ /! {x; s/\n / /g; p}')の処理
パターンスペースとホールドスペースの内容を置き換える。

パターンスペース hoge1: inu
ホールドスペース -

パターンスペース(hoge1: inu)の内容を出力する。
 

②2行目処理

2行目データがパターンスペースに読み込まれる

パターンスペース hoge2: neko
ホールドスペース -

1個目のスクリプト(-e '1 {h; $ !d}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース hoge2: neko
ホールドスペース -

2個目のスクリプト(-e '$ {x; s/\n / /g; p}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース hoge2: neko
ホールドスペース -

3個目のスクリプト(-e '/^ / {H; d}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース hoge2: neko
ホールドスペース -

4個目のスクリプト(-e '/^ /! {x; s/\n / /g; p}')の処理
パターンスペースとホールドスペースの内容を置き換える。

パターンスペース -
ホールドスペース hoge2: neko

パターンスペース(空)の内容を出力する。
 

③3行目処理

3行目データがパターンスペースに読み込まれる

パターンスペース kuma
ホールドスペース hoge2: neko

1個目のスクリプト(-e '1 {h; $ !d}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース kuma
ホールドスペース hoge2: neko

2個目のスクリプト(-e '$ {x; s/\n / /g; p}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース kuma
ホールドスペース hoge2: neko

3個目のスクリプト(-e '/^ / {H; d}')の処理
パターンスペースの内容をホールドスペースに追記

パターンスペース kuma
ホールドスペース hoge2: neko
kuma

パターンスペースを削除

パターンスペース -
ホールドスペース hoge2: neko
kuma

4個目のスクリプト(-e '/^ /! {x; s/\n / /g; p}')の処理
パターンスペースとホールドスペースを入れ替える

パターンスペース hoge2: neko
kuma
ホールドスペース -

改行+スペースをスペースに置換

パターンスペース hoge2: neko kuma
ホールドスペース -

パターンスペース(hoge2: neko kuma)の内容を出力する。

パターンスペース hoge2: neko kuma
ホールドスペース -

 

④4行目(最終行)処理

4行目データがパターンスペースに読み込まれる

パターンスペース hoge3: saru
ホールドスペース -

1個目のスクリプト(-e '1 {h; $ !d}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース hoge3: saru
ホールドスペース -

2個目のスクリプト(-e '$ {x; s/\n / /g; p}')の処理
パターンスペースとホールドスペースを入れ替える

パターンスペース -
ホールドスペース hoge3: saru

パターンスペース(空)の内容を出力する。

3個目のスクリプト(-e '/^ / {H; d}')の処理
アドレス条件が合致しない為、何も処理されない。

パターンスペース -
ホールドスペース hoge3: saru

4個目のスクリプト(-e '/^ /! {x; s/\n / /g; p}')の処理
パターンスペースとホールドスペースを入れ替える

パターンスペース hoge3: saru
ホールドスペース -

パターンスペース(hoge3: saru)の内容を出力する。

この処理の意味

aliasファイルの同一エントリに改行が含まれる場合を想定していると思われる。

# cat ./a
hoge1: inu
hoge2: neko
 kuma
hoge3: saru
# postalias ./a
# db_dump -p ./a.db                                                                                                                                                     
~省略~
 hoge1\00
 inu\00
 hoge3\00
 saru\00
 hoge2\00
 neko kuma\00
DATA=END
# 

 


※hoge2はnekoとkumaに変換される。

sort -ufについて

-u(unique)オプション

sort | uniqと同じ。

# cat ./a
a
b
c
a
b
c
# sort -u ./a
a
b
c
# 

 

-fオプション

大文字・小文字を問わず。

# cat ./a
a
B
c
A
b
C
# sort -uf ./a
a
B
c
# 

 

|tee >(sed 's/^/INFO: Mailman withlist: /' 1>&2)の意味

動きはこんな感じ

# cat ./a
a
b
c
# cat ./a | tee >(sed 's/^/INFO: Mailman withlist: /' 1>&2) | cat
a
b
c
INFO: Mailman withlist: a
INFO: Mailman withlist: b
INFO: Mailman withlist: c
# cat ./a | tee >(sed 's/^/INFO: Mailman withlist: /' 1>&2) | cat > /dev/null
INFO: Mailman withlist: a
INFO: Mailman withlist: b
INFO: Mailman withlist: c
# 

  


manのteeには、この使い方は記載なし。infoにあった。
どうやら"プロセス置換"という機能のよう。

# info coreutils 'tee invocation'

 


bashのプロセス置換機能を活用して、シェル作業やスクリプト書きを効率化する - 双六工場日誌
Bash のプロセス置換が便利な件 - 理系学生日記

こりゃまた便利な機能だ!!!!
以下のように使える。
①コマンド出力をファイルとして見立ててくれる。

# cat ./a
a
b
c
# cat ./b
a
b
d
# diff -u <(cat ./a) <(cat ./b)
--- /dev/fd/63  2016-12-04 21:30:23.743192617 +0900
+++ /dev/fd/62  2016-12-04 21:30:23.743192617 +0900
@@ -1,3 +1,3 @@
 a
 b
-c
+d
#

 


ファイルの出力結果をファイルに変換してくれている。


②コマンドによるファイルへの入力をコマンドで受ける事ができる。
 ※コマンドをファイルをように見立ててくれる。

# echo "test" | tee ./hoge
test
# cat ./hoge 
test
# echo "test" | tee >(sed 's/test/hoge/') | cat
test
hoge
#

 


teeは引数にファイルを取り、出力をファイルと標準出力にだす。
このファイル部分を">(コマンド)"で置換している。

testの-sオプション

ファイルが存在してサイズが0より大きい場合にマッチ

lsの-fと-Fオプション

-fオプション

manでは以下説明

do not sort, enable -aU, disable -ls --color

以下を有効にして
"-a" .付きファイルを出力
"-U" ソートせずディレクトリオーダー順に出す。

以下を無効にした時と同じ
"-l" ロングフォーマット出力
"-s" サイズを出力
"--color" 出力を色づけ

無駄を省いて高速にする意味だろう。
spool下は大量にファイルがあるから、-Uは効果が大きいかも。
"-a"オプションを付けるとソートが動いている。

# touch ./testdir/{1..10000}                                                                                             
# diff <(ls -a ./testdir/) <(ls -f ./testdir/) | wc -l
2898

 


★早速プロセス置換を利用!こりゃ便利!

-Fオプション

manでは、さらっと以下しか説明がない。

append indicator (one of */=>@|) to entries

Linuxコマンド【 ls 】ファイルとディレクトリのリストを表示 - Linux入門 - Webkaru
上記URLには、以下記載があり。

うお!こりゃまた便利。

# find / -type f | head -1 | xargs -i ls -dF {}
/proc/sysrq-trigger
# find / -type d | head -1 | xargs -i ls -dF {}
//
# find / -type l | head -1 | xargs -i ls -dF {}
/dev/initctl@
# find / -type p | head -1 | xargs -i ls -dF {}
/run/systemd/inhibit/1.ref|
# find / -type f -executable | head -1 | xargs -i ls -dF {}
/run/log/journal/f9370ed252a14f73b014c1301a9b6d1b/system@40f6700c2dd041cfb22614542a9d6998-0000000000000001-000542cc7deaa560.journal*
# 

 

sed 's/^>*From />&/' "spool/$n"は何をやっているか?

# echo  '>>From ' | sed -e 's/^>*From />&/g'
>>>From 
# 

 


リダイレクトが一文字増えた。これは&がパターンマッチした部分に置換される為。

こんな動きも抑えておく。

# echo  '>>From test' | sed -e 's/^>*From />&/g'
>>>From test
# 

sedコマンドの使い方

コマンドの役割

manでは、"stream editor for filtering and transforming text"
入力データをフィルタしたり置き換えたりする。

sedコマンドで指定する項目

①オプション

スクリプト

③読み込み対象(FILE or STDIN)

動作フロー

①1行データを読み込み、「パターンスペース」と呼ばれる記憶領域に保存
②パターンスペースに保存されているデータに対して処理を行う
③パターンスペースの内容を出力してパターンスペースを空にする
④①に戻り次の行の処理を行う
 

スクリプトの指定方法について

・-e,-E,-fオプションでスクリプトを指定
・-e,-Eはスクリプト文字列を引数に取る(違いは後述)
・-fはスクリプトが記述されたファイルを引数に取る

スクリプトの構成要素

・以下要素があり
 アドレス
 コマンド
・アドレスはコマンドの実行対象を指定。以下で指定が可能
 ①行情報
 ②正規表現
・アドレスは、範囲指定も可能
 例)n行目からm行目まで
 例)n行目からm行以降まで
 例)n行目からm行目毎
 例)正規表現Aにマッチした部分から正規表現Bにマッチした部分まで
・コマンドは処理内容を指定

実行例と動作説明

-nオプション有無の違い

例1) -nオプション無の場合
# cat ./a
a
b
# sed -e '/a/p' ./a                                                                                               
a
a
b
# 

 
①1行目(a)がパターンスペースに読み込まれる
②アドレスは正規表現で指定されている(/a/)
正規表現にマッチした1行名に対してpコマンド(結果を出力)が実行され標準出力に出力される
④パターンスペースにある1行目が標準出力に出力
⑤パターンスペースは空になる
⑥2行目(b)がパターンスペースに読み込まれる
正規表現にマッチしない為、pコマンドは実行されない
⑧パターンスペースのデータを標準出力する
⑨パターンスペースは空になる

「動作フロー」で記載したように、デフォルトでパターンスペースの行を標準出力する。
そのため、1行目が2回出力されている。

例2 -nオプション有の場合

例1の④・⑧が実行されなくなる。

# cat ./a
a
b
# sed -n -e '/a/p' ./a
a
# 

 

①1行目(a)がパターンスペースに読み込まれる
②アドレスは正規表現で指定されている(/a/)
正規表現にマッチした1行名に対してpコマンド(結果を出力)が実行され標準出力に出力される
④パターンスペースは空になる
⑤2行目(b)がパターンスペースに読み込まれる
正規表現にマッチしない為、pコマンドは実行されない
⑦パターンスペースは空になる

アドレスの指定方法

例1 行番号指定
# cat ./a
a
b
c
d
# sed -n -e '1p' ./a
a
# sed -n -e '$p' ./a
d
# 

 
"$"で最終行の指定が可能

例2 行番号範囲指定
# cat ./a
a
b
c
d
# sed -n -e '1,2p' ./a
a
b
# sed -n -e '1,+1p' ./a
a
b
# sed -n -e '0~2p' ./a
b
d
# sed -n -e '1~2p' ./a
a
c
# 

 
N,Mで行番号を指定した範囲指定が可能
N,+MでN行目からM行以降の範囲指定が可能
N~MでN行目からM行間隔で出力が可能

例3 正規表現による範囲指定
# cat ./a
a
b
c
d
a
b
c
d
# sed -n -e '/a/,/c/p' ./a
a
b
c
a
b
c
# sed -n -e '/a/~2' ./a
sed: -e expression #1, char 4: 不明なコマンド: `~'
#

 
PATTERN,PATTERNで範囲指定が可能
PATTERN~Nで行間隔指定はできない

他コマンド説明

-dコマンド

アドレスで指定された行のパターンスペースの削除を行う

# cat ./a
a
b
#
# sed -e '1d' ./a
b
#

 

①1行目(a)がパターンスペースに読み込まれる
②アドレスは1行目が指定されている
③アドレスで指定された1行目のパターンスペースに対してdコマンドが実行され、パターンスペースのデータが削除される
④パターンスペースにある1行目を標準出力するが、パターンスペースが削除済みの為、何も出力されない
⑤パターンスペースは空(既に削除済み)になる
⑥2行目(b)がパターンスペースに読み込まれる
⑦アドレスで指定されていないため、dコマンドは実行されない
⑧パターンスペースのデータを標準出力する
⑨パターンスペースは空になる

-sコマンド

アドレスで指定された行のパターンスペースの置換を行う。sコマンドは、パラメータに正規表現を指定する。

# cat ./a
a a
b b
a a
# sed -e '1s/a/b/g' ./a
b b
b b
a a
#

 

①1行目(a a)がパターンスペースに読み込まれる
②アドレスは1行目が指定されている
③アドレスで指定された1行目のパターンスペースに対してsコマンドが実行され、aがbに置換される
 /a/b/gがパラメータ。aをbに置換する。
 g指定により1行内にマッチする部分が複数ある場合、全て置換される。
 g指定がない場合は、最初の部分のみ置換
④置換されたパターンスペースを標準出力する
⑤パターンスペースは空になる
⑥2行目(b b)がパターンスペースに読み込まれる
⑦アドレスで指定されていないため、sコマンドは実行されない
⑧パターンスペースのデータを標準出力する
⑨パターンスペースは空になる
⑩3行目(a a)がパターンスペースに読み込まれる
⑪アドレスで指定されていないため、sコマンドは実行されない
⑫パターンスペースのデータを標準出力する
⑬パターンスペースは空になる

他オプション説明

-i[SUFFIX]オプション

ファイルを置換するオプション。
SUFFIX指定時は、指定した文字例を付与したファイル名で入力ファイルをバックアップする。

例) SUFFIX未指定時

# echo a > ./a
# ls -i a
52977918 a
# sed -i -e '/a/p' ./a
# ls -i a
52977919 a
# cat ./a 
a
a
# 

ポイントは入力ファイルを変更せず削除し、新たに同名ファイルで生成している点。
※元ファイルのデータは維持されない。

例) SUFFIX指定時

# echo a > ./a
# ls -i ./a      
52977918 ./a
# sed -i".hoge" -e '/a/p' ./a
# ls -i ./a*
52977919 ./a  52977918 ./a.hoge
# cat ./a
a
a
# cat ./a.hoge
a
#

ポイントは既存ファイルを削除せずにa.hogeにリネームして、新たにaを生成している点。
※元ファイルのデータは維持できる。


-cオプション

"-i"と同時指定時のみ動作するオプション。-cを指定すると元ファイルをSUFFIX付きファイルとしてコピーする。
※-iのみの場合はコピーではなくリネームする

例)

# echo a > ./a
# ls -i ./a 
52977918 ./a
# sed -c -i".hoge" -e '/a/p' ./a
# ls -i ./a ./a.hoge
52977918 ./a  52977920 ./a.hoge
# cat ./a
a
a
# cat ./a.hoge
a
# 

状況に応じて使い分けるべきだが、一般的な用途では-cを付ける必要はなさそう。
どちらも、a、a.hogeのデータは同じ。aが処理後、a.hogeが処理前のデータになる。

-Eオプション

スクリプトを指定する。

"-e"は基本、"-E"は拡張
どちらも提供する機能は同じ。以下文字をメタキャラクタとして使う場合、エスケープするかしないかの違い。

+ ? { } ( ) |

 

例)

# echo 'a' | sed -n -e '/a+/p'
# echo 'a' | sed -n -e '/a\+/p'
a
# echo 'a' | sed -n -E '/a+/p'
a
# echo 'a' | sed -n -E '/a\+/p'
# 

 

ホールドスペースの説明

パターンスペース以外の記憶領域

パターンスペース・パターンスペース操作コマンド

コマンド 説明
h パターンスペースの内容をホールドスペースにコピー
H パターンスペースの内容をホールドスペースの末尾に追加
g ホールドスペースの内容をパターンスペースにコピー
G ホールドスペースの内容をパターンスペースの末尾に追加

 

実行例

ファイル内容を逆順に出力する。

# cat ./a
a
b
c
# sed -n -e 'G' -e 'h' -e '$p' ./a
c
b
a

#

 

処理の流れ

①パターンスペースに1行目の"a"が読み込まれる

パターンスペース a
ホールドスペース


②Gコマンドでホールドスペースの内容がパターンスペースの末尾に追加される。しかし、ホールドスペースは空の為、変化はなし

パターンスペース a
ホールドスペース


③hコマンドでパターンスペースの内容がホールドスペースにコピーされる

パターンスペース a
ホールドスペース a


④pコマンドは最終行のみ実行される。まだ1行目なので何も実行されない

パターンスペース a
ホールドスペース a


⑤パターンスペースに2行目の"b"が読み込まれる

パターンスペース b
ホールドスペース a


⑥Gコマンドでホールドスペースの内容がパターンスペースの末尾に追加される

パターンスペース b
a
ホールドスペース a


⑦hコマンドでパターンスペースの内容がホールドスペースにコピーされる

パターンスペース b
a
ホールドスペース b
a


⑧pコマンドは最終行のみ実行される。まだ2行目なので何も実行されない

パターンスペース b
a
ホールドスペース b
a


⑨パターンスペースに3行目(最終行)の"c"が読み込まれる

パターンスペース c
ホールドスペース b
a


⑩Gコマンドでホールドスペースの内容がパターンスペースの末尾に追加される

パターンスペース c
b
a
ホールドスペース b
a


⑪hコマンドでパターンスペースの内容がホールドスペースにコピーされる

パターンスペース c
b
a
ホールドスペース c
b
a


⑫pコマンドが最終行の為、実行される。pコマンドは現在のパターンスペースの内容を出力する

パターンスペース c
b
a
ホールドスペース c
b
a


※上記の流れで大体説明はつく。しかし、出力結果には最後に空行が入っている。
②の時に空のホールドスペースをパターンスペースに追加する時に、改行が追加されたと想定される。

# sed -n -e 'G' -e 'p' ./a|cat -A
a$
$
b$
$
c$
$
#
# sed -n -e 'g' -e 'p' ./a|cat -A
$
$
$

 

おまけ

シングルクオートをパターンマッチ指定するのが面倒
sedでのシングルクォートの置換 | hiro345
 

# echo "''" | sed -E '/\'\'/p'
> ※エスケープが効かない
# echo "''" | sed -n -E '/'\'\''/p'
'' ※エスケープし更にシングルクオートで囲えばOK。直感的でなし。
# echo "''" | sed -n -E "/''/p"
'' ※ダブルクオートで囲えばOK

シェルの展開(EXPANSION)を学ぶ

man bashのEXPANTIONを勉強

EXPANSIONはコマンド実行前に処理されるコマンドラインの展開処理のこと。
7種類の動きがあり。

EXPANSIONの動きの確認の前に、shellで使われる括弧の英語/日本語表記について把握する。

括弧 - Wikipedia

表記 英語 日本語
( parenthesis, paren 丸括弧(小括弧)
{ brace 波括弧(中括弧)
[ bracket 角括弧(大括弧)

それでは7種類の動きを確認!!


①brace expansion

brace expansionとは中括弧{}による展開のこと。

man bashの"Brace Expansion"部分のGoogle翻訳によると、、、

ブレース拡張は、任意の文字列を生成するメカニズムです。このメカニズムはパス名拡張に似ていますが、生成されたファイル名は存在する必要はありません。中括拡張されるパターンはオプションのプリアンブルの形をとり、コンマで区切られた一連の文字列、または中括弧の間のシーケンス式の後にオプションの追記文字が続きます。プリアンブルは、中括弧内に含まれる各文字列の前に置かれ、ポストスクリプトは結果の各文字列に追加され、左から右に展開されます。

最近のGoogle翻訳すごい。。。

ひとまず以下だけ覚える。

シーケンス展開

# echo {a..c}
a b c
# echo {0..3}
0 1 2 3
# echo {0..3}a
0a 1a 2a 3a
# echo a{0..3}
a0 a1 a2 a3

カンマ区切り展開

# ls
a
# echo {a,b,1,2,*}                                                                                                                       
a b 1 2 a
# echo {a,b,1,2,*,?}
a b 1 2 a a
# echo {a,b,1,2,*,?,??}
a b 1 2 a a ??

注目すべきは*や?によるパス展開もされる点。
また*や?パス展開されない場合、そのまま文字として利用されてしまう点も注意。mkdirとかで使うと*や?のついたディレクトリが出来てしまう。


②tilde expansion

man bashの"Tilde Expansion"部分のGoogle翻訳によると、、、

単語が引用符で囲まれていないチルダ文字( `〜 ')で始まる場合、最初の引用符なしスラッシュ(または引用符なしのスラッシュがない場合はすべての文字)の前のすべての文字はチルダプレフィックスと見なされます。チルダプレフィックス内の文字のいずれも引用符で囲まれていない場合、チルダに続くチルダプレフィックス内の文字は、可能なログイン名として扱われます。このログイン名がヌル文字列の場合、チルダはシェルパラメータHOMEの値に置き換えられます。 HOMEが設定されていない場合は、シェルを実行しているユーザーのホームディレクトリが代わりに使用されます。それ以外の場合は、tilde-prefixが、指定されたログイン名に関連付けられたホームディレクトリに置き換えられます。

ひとまず以下だけ覚える。

ホームディレクトリに展開

# echo ~
/root
# echo ~/test
/root/test
# echo ~"/test"
~/test
# echo ~/"test"
/root/test
# echo a~
a~
# echo "a"~
a~

③parameter expansion

変数展開のこと。

man bashの"Parameter Expansion"部分のGoogle翻訳によると、、、

`$ '文字は、パラメータ展開、コマンド置換、または算術展開を導入します。展開されるパラメータ名またはシンボルは、省略可能な中括弧で囲むことができますが、名前の一部として解釈される直後の文字から拡張する変数を保護する役割を果たします。

色々記法・機能があるので、詳細は以下URLを参照。
シェルの変数展開 - Qiita
Bash Reference Manual: Shell Parameter Expansion

ひとまず以下だけ覚える。

{}(中括弧)を使った方がよい!(変数を明確に指定できるため)

# a="bbb"
# echo $a
bbb
# echo ${a}
bbb
# echo $ab

# echo ${a}b
bbbb

変数の文字数、配列の要素数の取り出し

${#parameter}
#を使う。

# a=1234
# echo ${#a}
4
# a=abcd
# echo ${#a}
4
# a=(a b c d)
# echo ${a}
a
# echo ${#a}
1
# echo ${a[@]}
a b c d
# echo ${#a[@]}
4
#

値の切り出し

パラメータ展開 内容
${parameter#word} 先頭から前方最短一致した位置まで取り除きます
${parameter##word} 先頭から前方最長一致した位置まで取り除きます
${parameter%word} 末尾から後方最短一致した位置まで取り除きます
${parameter%%word} 末尾から後方最長一致した位置まで取り除きます

前方一致の場合

# a=/var/log/messages
# echo ${a}
/var/log/messages
# echo ${a#/}
var/log/messages
# echo ${a##/}
var/log/messages
# echo ${a#*/}
var/log/messages
# echo ${a##*/}
messages
# echo ${a#log}
/var/log/messages
# echo ${a#*log}
/messages
# echo ${a##*log}
/messages

後方一致の場合

# a=/var/log/messages
# echo ${a%/}
/var/log/messages
# echo ${a%%/}
/var/log/messages
# echo ${a%/*}
/var/log
# echo ${a%%/*}

# echo ${a%log}
/var/log/messages
# echo ${a%%log}
/var/log/messages
# echo ${a%log*}
/var/
# echo ${a%%log*}
/var/

④command substitution

man bashの"Command Substitution"部分のGoogle翻訳によると、、、

コマンド置換は、コマンド名の代わりにコマンド出力を許可します。 2つの形式があります。
$(コマンド)
または
`コマンド`
Bashはコマンドを実行し、コマンド置換をコマンドの標準出力に置き換え、末尾に改行を削除して拡張を実行します。埋め込み改行は削除されませんが、単語分割中に削除される可能性があります。コマンド置換$(catファイル)は、同等ではあるが高速な$(<ファイル)で置き換えることができます。

ひとまず以下だけ覚える。

$(COMMAND)の形式の方が新しい使い方でよい!範囲指定が分かりやすく、ネストする時とか記述がシンプルになる。

# A=/etc/hosts
# echo $(cat $A)                                                                                                                             
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# echo `cat $A`
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# echo $(echo $(cat $A))
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# echo `echo `cat $A``
cat /etc/hosts
# echo `echo \`cat $A\``
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

⑤arithmetic expansion

man bashの"Command Substitution"部分のGoogle翻訳によると、、、

算術展開では、算術式の評価と結果の置換が可能です。
算術展開の形式は次のとおりです。
$((式))
式は二重引用符で囲まれているかのように扱われますが、かっこ内の二重引用符は特別に扱われません。式のすべてのトークンは、パラメータ拡張、文字列展開、コマンド置換、および見積りの削除を受けます。算術展開はネストすることができます。

"見積りの削除"は"quote removal"のこと。
Quoteはバックスラッシュとシングル・ダブルクオーテーションのこと。
"quote removal"は
After the preceding expansions, all unquoted occurrences of the characters \, ', and " that did not result from one of the above expansions are removed.
と書いてあるが、良く分からない。。。展開処理の最後に行われう処理のよう。ひとまず目をつむる。

ひとまず以下だけ覚える。

2重括弧の中に数式を書けば展開される!

# echo $((1+1))
2
# echo $((1*1))
1
# echo $((1/1))
1
# echo $((1%1))
0
# echo $((1**1))
1

数式以外を指定した時は、こんな動き

# a=/var/log/messages
# echo $((a))
-bash: /var/log/messages: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages")
# echo $(($a))
-bash: /var/log/messages: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages")
# echo $(($a+1))
-bash: /var/log/messages+1: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages+1")
# echo $(($a+a))
-bash: /var/log/messages+a: 構文エラー: オペランドが予期されます (エラーのあるトークンは "/var/log/messages+a")
# unset a
# echo $((a))
0
# echo $(($a))
0

変数展開やコマンド置換等も動く

# echo 1 > ./b
# echo $(($(cat ./b)+1))
2

⑥word splitting

man bashの"Word Splitting"部分のGoogle翻訳によると、、、

シェルは、単語の二重引用符で囲まれていないパラメータ展開、コマンド置換、および算術展開の結果をスキャンします。
シェルは、IFSの各文字を区切り文字として扱い、他の展開の結果をこれらの文字の単語に分割します。 IFSが設定されていない場合、またはその値が正確に の場合、既定値、前回の展開結果の先頭と最後に、および無視され、先頭または末尾にないIFS文字のシーケンスは単語を区切る役割を果たします。 IFSがデフォルト以外の値を持つ場合、空白文字がIFS(IFS空白文字)の値である限り、空白文字のスペースとタブのシーケンスは単語の先頭と末尾で無視されます。隣接するIFS空白文字と一緒にIFS空白ではないIFS内の文字は、フィールドを区切ります。一連のIFS空白文字も区切り文字として扱われます。 IFSの値がNULLの場合、ワード分割は行われません。
明示的なヌル引数( ""または " ')は保持されます。値を持たないパラメータの拡張の結果として、引用符で囲まれていない暗黙のヌル引数は削除されます。値のないパラメータが二重引用符で囲まれている場合、null引数が返され、保持されます。
展開が行われない場合、分割は実行されないことに注意してください。

良く分からないが、
二重引用符で囲まれていないパラメータ展開、コマンド置換、および算術展開の結果をスキャンして、結果を単語に分割する処理のことだろう。
重要そうだけど、ひとまず目をつむる。


⑦pathname expansion

man bashの"Word Splitting"部分のGoogle翻訳によると、、、

単語の分割後、-fオプションが設定されていない限り、bashは各単語に*、?、および?の文字をスキャンします。これらの文字のいずれかが表示された場合、その単語はパターンとみなされ、パターンに一致するファイル名のアルファベット順ソートリストに置き換えられます。一致するファイル名が見つからず、シェルオプションnullglobが有効でない場合、その単語は変更されません。 nullglobオプションが設定されていて一致するものが見つからない場合、その単語は削除されます。 failglobシェルオプションが設定されていて一致するものが見つからない場合、エラーメッセージが出力され、コマンドは実行されません。シェルオプションnocaseglobを有効にすると、アルファベット文字の大文字と小文字を区別せずに一致が実行されます。パス名展開にパターンを使用する場合、シェルオプションのdotglobが設定されていない限り、名前の先頭またはスラッシュの直後の文字「。」は明示的に一致させる必要があります。パス名をマッチさせる場合、スラッシュ文字は常に明示的に一致させる必要があります。他の場合には、 ``。 ''文字は特別に扱われません。 nocaseglob、nullglob、failglob、およびdotglobシェルオプションについては、SHELL BUILTIN COMMANDSのshoptの説明を参照してください。

またまた難しい。ひとまず以下を覚える

Word splitingの後に動いて*、?、[]の展開をする。デフォルトでは、マッチするパス(ファイルorディレクトリetc)がない場合、パターンマッチ文字は、そのままで認識される。

と覚える

# ls
a  b
# ls * ?
a  a  b  b
# ls ??
ls: ?? にアクセスできません: そのようなファイルやディレクトリはありません
# ls [ab]
a  b
# ls [cd]
ls: [cd] にアクセスできません: そのようなファイルやディレクトリはありません
# 

おまけ

システムによっては、process substitutionもあり。

man bashの"Process Substitution"部分のGoogle翻訳によると、、、

プロセス置換は、名前付きパイプ(FIFO)をサポートするシステム、または開いているファイルに名前を付ける/ dev / fdメソッドでサポートされています。 <(list)または>(list)の形式をとります。プロセスリストは、その入力または出力がFIFOまたは/ dev / fdのあるファイルに接続されて実行されます。このファイルの名前は、展開の結果として現在のコマンドに引数として渡されます。 >(リスト)フォームが使用されている場合、ファイルへの書き込みはリストの入力を提供します。 <(リスト)形式を使用する場合、引数として渡されたファイルは、listの出力を得るために読み込まれなければなりません。
使用可能な場合、プロセス置換は、パラメーターおよび変数の拡張、コマンド置換、および算術拡張と同時に実行されます。

Linuxシェルから辞書を呼び出す

いろんな環境で使いたいので、実装1ライナー

# JISYO="$HOME/bin/myjisyo" && JISYO_DATA="$HOME/myjisyo_data/jisyo.txt" && mkdir -p $(dirname $JISYO) $(dirname $JISYO_DATA) && wget -q -O - http://www.namazu.org/~tsuchiya/sdic/data/gene95.tar.gz | tar xzf - -O gene.txt | tr -d '\r' | iconv -c -f shift-jis -t utf-8 > $JISYO_DATA && echo "egrep -i -A 1 --color \"^\$1\$\" $JISYO_DATA" > $JISYO && chmod u+x $JISYO
# myjisyo apple
apple
リンゴ,りんご,リンゴの木

線形回帰とは?

メモ

たくさんの変数を持つ集まり (= ベクトル) についてある関数にノイズが加わったような状態が事例として観測されました。このときその関数を予測しましょうというのが回帰分析です。

関数を予測するのが線形分析?
※1次関数だけでなく対数関数も

Jubatus外れ値検知機能を使い不正ログイン検知

ためしに作ってみる!

GeoIPセットアップ

# rpm -ivh ftp://195.220.108.108/linux/centos/7.2.1511/os/x86_64/Packages/GeoIP-devel-1.5.0-9.el7.x86_64.rpm
# pip install geoip2
# wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz
# gunzip GeoLite2-City.mmdb.gz

機能

ユーザー単位で解析する。ログイン傾向から不正ログインを検知する
入力データは以下とする。
ログイン時間,接続元IP

入力データをプログラムで以下データに変換し学習データとする。

①接続元IP(ip_int)

②接続元IPの地域情報(geoid)

③接続元IPのDNSBL登録状況(dns_info)

④該当IPが新規接続元IPであるかの有無(first)

⑤対象アカウントへの過去M分以内のログイン回数(num_of_login_in_near_time)

⑥対象アカウントへの過去M分以内の接続元地域数(num_of_location_in_near_time)
 別の場所からのログインは怪しく見えるから。

誤検知をできるだけ防ぐべく、多次元データで外れ値検知をしてみる。

まだ軽くしか動かしてないので、しっかり動くか分かりません!!

プログラム

login_analyze.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import signal
import sys
import os
import json
from jubatus.anomaly import client
from jubatus.common import Datum
import time
import geoip2.database
import wget
import gzip
import dns.resolver
import syslog

DEBUG = True


def exec_study(uname, data, mes):
    stime = time.time()

    ip_int = data[0]
    geoid = data[1]
    dns_info = data[2]
    first = data[3]
    num_of_login_in_near_time = data[4]
    num_of_location_in_near_time = data[5]

    datum = Datum()

    datum.add_number("ip_int", float(ip_int))
    datum.add_number("geoid", float(geoid))
    datum.add_number("dns_info", float(dns_info))
    datum.add_number("first", float(first))
    datum.add_number("num_of_login_in_near_time", float(num_of_login_in_near_time))
    datum.add_number("num_of_location_in_near_time", float(num_of_location_in_near_time))
    anom = client.Anomaly("127.0.0.1", 9199, uname)
    ret = anom.add(datum)
    output_log(str(ret) + ": " +  mes)


def op_srv(OP):
    if OP[0] == "start":
        com = "jubaanomaly -f " + OP[1] + "> /dev/null 2>&1 &"
    else:
        com = "pkill jubaanomaly > /dev/null 2>&1"
    if os.system(com):
        err_fin("jubaanomaly " + OP[0] + " failed")


def ip2int(ip):
    o = map(int, ip.split('.'))
    ip_int = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
    return ip_int


def output_log(mes):
    syslog.openlog()
    syslog.syslog(mes)
    syslog.closelog()
    if DEBUG:
        print(mes)


def err_fin(mes):
    print("Error: " + mes)
    output_log("Error " + mes)
    op_srv(["stop"])
    exit(1)


def get_dnsinfo(ip):
    bls = ["zen.spamhaus.org", "xbl.spamhaus.org", "pbl.spamhaus.org"]
    answers = list()
    for bl in bls:
        my_resolver = dns.resolver.Resolver()
        query = '.'.join(reversed(str(ip).split("."))) + "." + bl
        try:
            answers += my_resolver.query(query, "A")
        except dns.resolver.NXDOMAIN:
            pass
        except:
            err_fin("dns loolup failed")
    return len(answers)


def get_location(ip):
    db_file = "./GeoLite2-City.mmdb"
    if not os.path.isfile(db_file):
        ret = os.system("wget -q http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz; gunzip -f ./GeoLite2-City.mmdb.gz")
        if ret:
            print("Error: wget failed")
    reader = geoip2.database.Reader(db_file)

    country = str()
    geoid = int()

    try:
        response = reader.city(ip)
    except:
        geoid = 0
        country = "UNKNOWN"
    else:
        geoid = response.city.geoname_id
        country = response.country.name
        if not geoid:
            geoid = 0
        if not country:
            country = "UNKNOWN"
    return geoid, country


def get_args():
    if len(sys.argv) != 3:
        err_fin(" Invalid args")

    config = sys.argv[1]
    uname = sys.argv[2]

    if not os.path.isfile(config):
        err_fin(config + " does not exist")
    return config, uname


def do_exit(sig, stack):
    print('You pressed Ctrl+C.')
    print('Stop running the job.')
    sys.exit(0)


def main():
    signal.signal(signal.SIGINT, do_exit)
    config, uname = get_args()

    ips = set()
    login_data = list()

    op_srv(["start",config])
    time.sleep(5)
 
    while True:
        try:
            line = raw_input()
        except EOFError:
            op_srv(["stop"])
            exit()

        if not line:
            next

        ltime = int(line.split(",")[0])
        ip = line.split(",")[1]
        ip_int = ip2int(ip)

        geoid, country = get_location(ip)

        dns_info = get_dnsinfo(ip)

        if ip in ips:
            first = 0
        else:
            first = 1
        ips.add(ip)

        login_data.append([ltime, geoid])

        near_time_info = [i for i in login_data if ltime - (60 * 60 * 3) <= i[0] <= ltime]
        num_of_login_in_near_time = len(near_time_info)

        tmp_set = set()
        for i in near_time_info:
            tmp_set.add(i[1])
        num_of_location_in_near_time = len(tmp_set)

        mes = "uname: " + uname + " "
        mes += "ltime: " + str(ltime) + " "
        mes += "ip: " + ip + " "
        mes += "ip_int: " + str(ip_int) + " "
        mes += "country: " + country + " "
        mes += "geoid: " + str(geoid) + " "
        mes += "dns_info: " + str(dns_info) + " "
        mes += "first: " + str(first) + " "
        mes += "num_of_login_in_near_time: " + str(num_of_login_in_near_time) + " "
        mes += "num_of_location_in_near_time: " + str(num_of_location_in_near_time) + " "

        data = [ip_int, geoid, dns_info, first, num_of_login_in_near_time, num_of_location_in_near_time]

        exec_study(uname, data, mes)


if __name__ == '__main__':
    main()

コンフィグファイル

login_analyze.json

{
 "method" : "lof",
 "parameter" : {
  "nearest_neighbor_num" : 10,
  "reverse_nearest_neighbor_num" : 30,
  "method" : "euclid_lsh",
  "ignore_kth_same_point" : true,
  "parameter" : {
   "hash_num" : 8,
   "table_num" : 16,
   "probe_num" : 64,
   "bin_width" : 10,
   "seed" : 1234
  }
 },

 "converter" : {
  "string_filter_types": {},
  "string_filter_rules": [],
  "num_filter_types": {},
  "num_filter_rules": [],
  "string_types": {},
  "string_rules": [{"key":"*", "type":"str", "global_weight" : "bin", "sample_weight" : "bin"}],
  "num_types": {},
  "num_rules": [
    {"key" : "ip_int", "type" : "str"},
    {"key" : "geoid", "type" : "str"},
    {"key" : "dns_info", "type" : "num"},
    {"key" : "first", "type" : "num"},
    {"key" : "num_of_login_in_near_time", "type" : "num"},
    {"key" : "num_of_location_in_near_time", "type" : "num"}
  ]
 }
}

入力データ作成用ちょいスクリプト

make_data.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re
import signal
import datetime
import time
import sys
import os
from operator import itemgetter


def do_exit():
    exit(1)

signal.signal(signal.SIGINT, do_exit)

data = list()
for log in sys.argv[1:]:
    if os.path.isfile(log):
        ext = log.split(".")
        if not ext[1]:
            next
        y = (ext[1][0:4])

        for line in open(log, 'r'):
            line = line.strip()
            ptrn1 = re.compile(r'^(\S+)\s+(\d+)\s+(\d{2}:\d{2}:\d{2})(.*)$')
            mobj1 = ptrn1.match(line)
            if mobj1:
                m = mobj1.group(1)
                d = mobj1.group(2)
                t = mobj1.group(3)
                obj = datetime.datetime.strptime(str(y) + m + str(d) + t, '%Y%b%d%H:%M:%S')
                utime = int(time.mktime(obj.timetuple()))
                # print(utime)

                mes = mobj1.group(4)

                # SSHログイン
                ptrn2 = re.compile(r'^.*Accepted publickey for .* from (\S+) .*$')
                mobj2 = ptrn2.match(mes)
                if mobj2:
                    ip = mobj2.group(1)
                    data.append([utime, ip])

                # 他アプリのログインデータを追加
                #ptrn2 = re.compile(r'^.*Accepted publickey for .* from (\S+) .*$')
                #mobj2 = ptrn2.match(mes)
                # if mobj2:
                #  ip = mobj2.group(1)
                #  data.append([utime, ip])


data.sort(key=itemgetter(0))

for i in data:
    print(str(i[0]) + "," + i[1])

実行方法

# python ./make_data.py  /var/log/secure.20161010 /var/log/secure.20160918 | python ./login_analyze.py ./login_analyze.json hoge