30億のデバイスで走るとやばい危険シェル芸デバイス作った

Pocket
LINEで送る

年が明けました。昨年は

という騒ぎがありましたが、今年はシェル芸ポリスとして、荒ぶるシェル芸人を取り締まるふりをする所存です。出動!ミニスカポリス。

違います。

本題

今日は年明けの講義のためにデバイスドライバの例を書いておりました。ただ、今日は元日です。酒を飲みながら書いていたので、なんでこうなったかよくわからんのですが、気がついたら危険シェル芸のコマンドを吐く擬似デバイスができていました。

READMEを読んでも良く分からないと思いますので、解説を。

擬似デバイスとは

/dev/nullとか/dev/urandomとか、裏側に機械がくっついてないのに機械とのインタフェースの役割をするもののことです。/dev/nullとかのことは「デバイスファイル」と呼びます。「擬似デバイス」というのは「デバイスファイルを入出力口にして働く仮想の機械」というニュアンスの言葉です。

UNIX屋さんなら何か調査するときに、デバイスファイルを次のように使ったりします。

###コマンドの性能を測る(画面に文字を出して遅くならないように/dev/nullを使う)###
$ time grep -r hoge ~/ > /dev/null
###128バイトの大きさのファイルを作る###
uedamb:~ ueda$ head -c 128 /dev/urandom > a
uedamb:~ ueda$ ls -l a
-rw-r--r-- 1 ueda staff 128  1  1 22:32 a

要はデータを吸い込んだり、生成したりする特別なファイルということになります。なぜコマンドやデーモンによるサービスの体裁を取らないかというと、ファイルの方がcatやリダイレクトで簡単に使えるからです。データハンドリングはファイルシステムで、というのはUNIXがUNIXであるための大前提のようなものです。日経Linuxの去年の連載では、「ロボットでも基本はファイル入出力で」というテーマで執筆したのですが、それもこういうUNIXの考え方を意識したものでした。

どうやって作るのか

デバイスファイルの裏側にはデバイスドライバがいます。デバイスドライバをしかるべき方法でコーディングすれば、擬似デバイスを作ることができます。字を受けたときにどんな反応をするかをデバイスドライバ内に書けば、機械の代わりにそのコードが仕事をしてくれるわけです。本物の機械のためのデバイスドライバを書くときは(と言っても私は経験がほとんどありませんが)、デバイスドライバ内で機械のレジスタをいじることになります。

デバイスドライバはカーネルの一部として動くので、デバイスドライバのコードはカーネルを書くように書かねばなりません。printfとかmallocが使えなかったり、いろいろ面倒なので、その分敷居は少し高くなります。

あんまり詳しくは書けないのですが、コードをざっと見ていただければと。

使い方

今のところ、Ubuntu Linux 14.04でしか動作確認してません。今日作り始めたので当たり前ですが・・・。ということで、以下の操作をUnbuntuか、もし試していただけるなら別のLinux環境で行います。apt-getでgitとかmakeとかをインストールする必要があります。また、Linuxのヘッダファイルがないとコンパイルできないので、もしかしたらそれもapt-getする必要があるかもしれません。私はサーバ版でコンパイルしましたが、ヘッダはすでにあったのでapt-getする必要はありませんでした。

$ git clone https://github.com/ryuichiueda/KikenShellGeiDevice.git
$ cd KikenShellGeiDevice/
$ make

次のように「kiken.ko」というファイルができていたらOK。

$ ls kiken.ko
kiken.ko

デバイスドライバは、insmodというコマンドでカーネルに組み込みます。

$ sudo insmod kiken.ko
###カーネルから外したかったら以下のように###
$ sudo rmmod kiken 

次のように、「/dev/kiken0」というファイルができているはずです。

$ ls -l /dev/kiken0 
crw------- 1 root root 249, 0  1月  1 23:03 /dev/kiken0
###読み込みできるようにしておきましょう###
$ sudo chmod +r /dev/kiken0 

使う(危険)

なんだか薄くて真面目な解説になっちまってましたがここからが本番です。headで打ち切ってますが、アレなワンライナーがランダムに出続けます。ネタはココから。

$ cat /dev/kiken0 | head
sudo yum -y remove python*
sudo yum -y remove python*
echo '部長はヅラ' >> /etc/motd
echo ログ集計乙wwwww>> /var/log/httpd/access_log
rsync -av --delete /tmp/ ~/
yes | xargs -P 0 yes
for x in `seq 1 1 10000`; do wall '我はroot。神だ' ; done
crontab -r
echo {a..z}{a..z}{a..z}{a..z}{a..z}{a..z}{a..z}{a..z}{a..z}{a..z}{a..z}{a..z}{a..z}
rsync -av --delete /tmp/ ~/

危険極まりない。本当に何を考えているんだ。

パイプと相性抜群

とりあえず、自分のマシンを壊していいのなら、次のような使い方が標準になると思います。何の標準か知らんけど。

$ cat /dev/kiken0 | sudo bash

試さない方が良いです。何の責任も取れないことはGPLのライセンスの中に書いてあリ・・・。

安全に試す方法

かと言って何も試さないのも面白くないので、次のワンライナーを提示しておきます。grepにつけた-aは、grepがバイナリとして危険シェル芸を扱ってしまうので、文字列として扱ってくれと明示的に指示するためのオプションです。

$ cat /dev/kiken0 | grep -a 高須 | bash 

今後

オプションを充実させたり、標準入力から読み込んだ文字を関数の名前にしてfork爆弾を返す機能を実装する・・・っていうか講義の準備します。

本当に冗談です。

寝る。

20160102追記: テスト用VPSサーバにインストール。いい感じだ。

スクリーンショット 2016-01-02 10.05.42

20160102追記: Raspberry Pi 2用のMakefileを追加。

Pocket
LINEで送る

【問題と解答】第17回ジュンク堂はシェル芸が乗っ取った勉強会

Pocket
LINEで送る

ルール

  • ワンライナーで出されたお題を解きます。
  • 汎用的な解を考えるのは出された問題をとりあえず解いてから。
  • 特にどの環境とは指定しないので各自環境に合わせて読み替えを。ただし今回、AWKだけはGNU Awk 4.0.1を使っていると明記しておきます。
  • 今回のテーマはAWKですが、何で解いても構いません。別にPowerShellだろうがRubyだろうが構いません。ワンライナーじゃないけどエクセル方眼紙でも。

環境

今回はLinuxで解答例を作りましたので、BSDやMacな方は以下の表をご参考に・・・。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のようなデータを

$ cat data1
a 1
b 4
a 2
a 3
b 5

次のように変換してみましょう。

a 1 2 3
b 4 5

余力のある人は次のようなJSON形式にしてみましょう。

{a:[1,2,3],b:[4,5]}

解答

連想配列にデータを追記していって最後に出力するのが楽な方法です。

$ cat data1 | awk '{d[$1]=d[$1]" "$2}END{for(k in d){print k d[k]}}' 
a 1 2 3
b 4 5

JSONにするには力技(しか思い浮かばなかった)。

$ cat data1 | awk '{d[$1]=d[$1]" "$2}END{for(k in d){print k d[k]}}' |
 awk -v q='"' '{printf q$1q":[";for(i=2;i<=NF;i++){printf $i","};print "]"}' |
 xargs | tr ' ' ',' | awk '{print "{"$0"}"}' | sed 's/,]/]/g'
{a:[1,2,3],b:[4,5]}

Q2

以下の数字のファイルから同じレコード(行)があるかないかを調べ、ある場合には何行目と何行目にあるのか出力しましょう。

$ cat data
0.5937836043 0.4644710001
0.3637036697 0.5593602512
0.5655269331 0.6793148112
0.7804610574 0.2905477797
0.3637036697 0.5593602512

解答

$ cat data | awk 'a[$0]{print a[$0],NR,$0}{a[$0]=NR}'

1千万行でも10秒くらいで答えが出ることを確認済みです。もっと大きなレコード数で行う場合はもう一捻り必要です。

Q3

次のJSONのデータについて、aに対応づけられた配列内の数字の合計とbに対応づけられた配列内の数字の合計を求めましょう。

$ cat data
{"a":[1,2,3],"b":[4,5]}

解答

きれいな方法が思い浮かばないので力技で。

$ grep -o '"[ab]":\[[^\[]*\]' data | tr '":[],' '     ' |
 awk '{n=0;for(i=2;i<=NF;i++){n+=$i};print $1,n}'
a 6
b 9
$ cat data | jq . | tr -dc '[:alnum:]\n' |
 awk '/[ab]/{k=$1}!/[ab]/{n[k]+=$1}END{for(k in n){print k,n[k]}}'
a 6
b 9
###jqを使う例を。もっとうまくできるようですが・・・。###
$ cat data | jq 'reduce .a[] as $n (0; . + $n),reduce .b[] as $n (0; . + $n)'
6
9

Q4

次のようなIPv6アドレスをechoした後にパイプでコマンドをつなぎ、「::」で省略されているセクションに0を補ってください。

$ echo 2001:db8::9abc

ただし、同じワンライナーが

::1

でも使えるようにしてください。

解答

whileを使ってNFが8になるまでフィールドを補ってから処理してやると素直な処理になります。初めてシェル芸勉強会でawkのwhileを使いました・・・。

$ echo 2001:db8::9abc |
 awk -F: '{while(NF!=8){gsub(/::/,":0::",$0)};for(i=1;i<=8;i++){$i=$i!=""?$i:0};print}' |
 tr ' ' ':'
2001:db8:0:0:0:0:0:9abc
$ echo ::1 |
 awk -F: '{while(NF!=8){gsub(/::/,":0::",$0)};for(i=1;i<=8;i++){$i=$i!=""?$i:0};print}' |
 tr ' ' ':'
0:0:0:0:0:0:0:1
###別解###
$ echo 2001:db8::9abc |
 awk -F: '{while(NF!=8){gsub(/::/,":0::",$0)}print}' |
 tr ':' '\n' | awk '!NF{print 0}NF{print}' | xargs | tr ' ' ':'
Pocket
LINEで送る

【問題のみ】第17回ジュンク堂はシェル芸が乗っ取った勉強会

Pocket
LINEで送る

ルール

  • ワンライナーで出されたお題を解きます。
  • 汎用的な解を考えるのは出された問題をとりあえず解いてから。
  • 特にどの環境とは指定しないので各自環境に合わせて読み替えを。ただし今回、AWKだけはGNU Awk 4.0.1を使っていると明記しておきます。
  • 今回のテーマはAWKですが、何で解いても構いません。別にPowerShellだろうがRubyだろうが構いません。ワンライナーじゃないけどエクセル方眼紙でも。

環境

今回はLinuxで解答例を作りましたので、BSDやMacな方は以下の表をご参考に・・・。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のようなデータを

$ cat data1
a 1
b 4
a 2
a 3
b 5

次のように変換してみましょう。

a 1 2 3
b 4 5

余力のある人は次のようなJSON形式にしてみましょう。

{a:[1,2,3],b:[4,5]}

Q2

以下の数字のファイルから同じレコード(行)があるかないかを調べ、ある場合には何行目と何行目にあるのか出力しましょう。

$ cat data
0.5937836043 0.4644710001
0.3637036697 0.5593602512
0.5655269331 0.6793148112
0.7804610574 0.2905477797
0.3637036697 0.5593602512

Q3

次のJSONのデータについて、aに対応づけられた配列内の数字の合計とbに対応づけられた配列内の数字の合計を求めましょう。

$ cat data
{"a":[1,2,3],"b":[4,5]}

Q4

次のようなIPv6アドレスをechoした後にパイプでコマンドをつなぎ、「::」で省略されているセクションに0を補ってください。

$ echo 2001:db8::9abc

ただし、同じワンライナーが

::1

でも使えるようにしてください。

Pocket
LINEで送る

【問題と解答例】第13回危険でない方のシェル芸勉強会

Pocket
LINEで送る

環境

Macで解答を作ったのでLinuxな方は次のようにコマンドの読み替えを。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のようにShift JISのファイルを作り、Shift JISで「きく」と書いてあるファイルを探すワンライナーを考えてください。(答えは「b」ですね。)

uedambp:q1 ueda$ echo あいうえお | nkf -xLws > a
uedambp:q1 ueda$ echo かきくけこ | nkf -xLws > b
uedambp:q1 ueda$ echo さしすせそ | nkf -xLws > c

解答

uedambp:q1 ueda$ for f in * ; do nkf -w $f | grep -q きく && echo $f ; done
b

Q2

次のようにディレクトリa,b,c,dに1,2,…,9というファイルがあります。各ディレクトリ内のファイル数をワンライナーで数えてください。

uedambp:q2 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   └── 3
├── b
│   ├── 4
│   └── 5
├── c
└── d
    ├── 6
    ├── 7
    ├── 8
    └── 9

解答

uedambp:q2 ueda$ find . | tr / ' ' | awk 'NF==3{print $2}' | uniq -c
   3 a
   2 b
   4 d
uedambp:q2 ueda$ find . -type f | awk -F/ '{print $2}' | uniq -c
   3 a
   2 b
   4 d
###cも出したければ・・・###
uedambp:q2 ueda$ find . | awk -F/ '{print $2}' |
uniq -c | awk 'NF==2{print $2,$1-1}'
a 3
b 2
c 0
d 4
uedambp:q2 ueda$ ls * | xargs | gsed 's/.:/\n&/g' | awk '{print $1,NF-1}'
 -1
a: 3
b: 2
c: 0
d: 4
uedambp:q2 ueda$ ls -d * |
while read d ; do echo -n $d" " ; ls $d | gyo ; done
a 3
b 2
c 0
d 4

Q3

今度は次のような配置でファイル1,2,…,9が置かれているときに、ワンライナーでa、cの下のファイルの総数をカウントしてください(ディレクトリを除く)。つまりaなら5個、cなら4個が正解です。

uedambp:q3 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   ├── 3
│   └── b
│       ├── 4
│       └── 5
└── c
    └── d
        ├── 6
        ├── 7
        ├── 8
        └── 9

解答

uedambp:q3 ueda$ find . -type f | awk -F/ '{print $2}' | uniq -c
   5 a
   4 c

Q4

まず、次のように8桁日付のファイルを作ります。

uedambp:q4 ueda$ seq -w 1 31 | xargs -I@ touch 201401@
uedambp:q4 ueda$ ls
20140101  20140107  20140113  20140119  20140125  20140131
20140102  20140108  20140114  20140120  20140126
20140103  20140109  20140115  20140121  20140127
20140104  20140110  20140116  20140122  20140128
20140105  20140111  20140117  20140123  20140129
20140106  20140112  20140118  20140124  20140130

曜日別にディレクトリを作り、その中に当該するファイルを放り込んでください。

解答

uedambp:q4 ueda$ ls | gdate -f - '+%Y%m%d %a' |
while read d w ; do mkdir -p $w ; mv $d $w ; done
###英語のディレクトリにする###
uedambp:q4 ueda$ ls | LANG=C gdate -f - '+%Y%m%d %a' |
while read d w ; do mkdir -p $w ; mv $d $w ; done

Q5

以下のようにa,b,cというディレクトリを作り、その下に「{a,b,c}数字」というファイルを作ります。ファイル名の1文字目とディレクトリ名が一致するようにファイルを移動してください。

uedambp:q5 ueda$ tree
.
├── a
│   ├── a01
│   └── b01
├── b
│   ├── a02
│   ├── a03
│   └── c01
└── c
    └── a04

解答

uedambp:q5 ueda$ find . -type f |
awk '{print "mv",$1,substr($1,5,1)}'  | sh
mv: ./a/a01 and a/a01 are identical    ←エラーが出るけど大丈夫
uedambp:q5 ueda$ tree
.
├── a
│   ├── a01
│   ├── a02
│   ├── a03
│   └── a04
├── b
│   └── b01
└── c
    └── c01

Q6

次のようにディレクトリa, b, cの下に、8桁日付のファイルをいくつか置きます。

uedambp:q6 ueda$ tree
.
├── a
│   ├── 20130120
│   ├── 20140901
│   └── 20141021
├── b
│   ├── 20131011
│   └── 20140202
└── c
    ├── 20110202
    ├── 20130224
    └── 20141224

各ディレクトリの最新日付のファイルをカレントディレクトリ(a,b,cのあるディレクトリ)にコピーしてください。各ディレクトリの最新ファイルの日付はそれぞれ違い、コピーの際に衝突しないこととします。

解答

uedambp:q6 ueda$ for d in * ; do ls $d | tail -n 1 |
 xargs -n 1 -I@ cp $d/@ ./ ; done
###確認###
uedambp:q6 ueda$ ls
20140202  20141021  20141224  a  b  c
uedambp:q6 ueda$ find . -type f | tr '/' ' ' |
 awk '{f[$2]=f[$2]<$3?$3:f[$2]}END{for(k in f){print k,f[k]}}' |
 tr ' ' '/' | xargs -n 1 -I@ cp @ ./
###Tukubai等###
uedambp:q6 ueda$ find . -type f | tr '/' ' ' | sort | getlast 1 2 |
 tr '/' ' ' | awk '{print "cp", "./" $2 "/" $3 " ./"}' | sh

Q7

Q6について、適当にファイルをtouchします。今度はタイムスタンプが最新のファイルを、a, b, cそれぞれからカレントディレクトリにコピーしてください。コピーの際にタイムスタンプを変えない事。

解答

uedambp:q7 ueda$ for d in * ; do ls -t $d | head -n 1 |
 xargs -I@ -n 1 cp -p $d/@ ./ ; done

Q8

次のように5個ファイルを作ります。file1をfile2, file2をfile3, file3をfile4, file4をfile5, file5をfile1にmvしてください。

uedambp:q8 ueda$ for i in 1 2 3 4 5 ; do echo $i > file$i ; done
uedambp:q8 ueda$ head *
==> file1 <==
1

==> file2 <==
2

==> file3 <==
3

==> file4 <==
4

==> file5 <==
5

解答

uedambp:q8 ueda$ ls | 
awk 'BEGIN{a="tmp"}{print a,$1;a=$1}END{print a,"tmp"}' | 
tail -r | awk '{print "mv",$1,$2}' | sh
uedambp:q8 ueda$ head *
==> file1 <==
5

==> file2 <==
1

==> file3 <==
2

==> file4 <==
3

==> file5 <==
4
Pocket
LINEで送る

【問題のみ】第13回危険でない方のシェル芸勉強会

Pocket
LINEで送る

環境

Macで解答を作ったのでLinuxな方は次のようにコマンドの読み替えを。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のようにShift JISのファイルを作り、Shift JISで「きく」と書いてあるファイルを探すワンライナーを考えてください。(答えは「b」ですね。)

uedambp:q1 ueda$ echo あいうえお | nkf -xLws > a
uedambp:q1 ueda$ echo かきくけこ | nkf -xLws > b
uedambp:q1 ueda$ echo さしすせそ | nkf -xLws > c

Q2

次のようにディレクトリa,b,c,dに1,2,…,9というファイルがあります。各ディレクトリ内のファイル数をワンライナーで数えてください。

uedambp:q2 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   └── 3
├── b
│   ├── 4
│   └── 5
├── c
└── d
    ├── 6
    ├── 7
    ├── 8
    └── 9

Q3

今度は次のような配置でファイル1,2,…,9が置かれているときに、ワンライナーでa、cの下のファイルの総数をカウントしてください(ディレクトリを除く)。つまりaなら5個、cなら4個が正解です。

uedambp:q3 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   ├── 3
│   └── b
│       ├── 4
│       └── 5
└── c
    └── d
        ├── 6
        ├── 7
        ├── 8
        └── 9

Q4

まず、次のように8桁日付のファイルを作ります。

uedambp:q4 ueda$ seq -w 1 31 | xargs -I@ touch 201401@
uedambp:q4 ueda$ ls
20140101  20140107  20140113  20140119  20140125  20140131
20140102  20140108  20140114  20140120  20140126
20140103  20140109  20140115  20140121  20140127
20140104  20140110  20140116  20140122  20140128
20140105  20140111  20140117  20140123  20140129
20140106  20140112  20140118  20140124  20140130

曜日別にディレクトリを作り、その中に当該するファイルを放り込んでください。

Q5

以下のようにa,b,cというディレクトリを作り、その下に「{a,b,c}数字」というファイルを作ります。ファイル名の1文字目とディレクトリ名が一致するようにファイルを移動してください。

uedambp:q5 ueda$ tree
.
├── a
│   ├── a01
│   └── b01
├── b
│   ├── a02
│   ├── a03
│   └── c01
└── c
    └── a04

Q6

次のようにディレクトリa, b, cの下に、8桁日付のファイルをいくつか置きます。

uedambp:q6 ueda$ tree
.
├── a
│   ├── 20130120
│   ├── 20140901
│   └── 20141021
├── b
│   ├── 20131011
│   └── 20140202
└── c
    ├── 20110202
    ├── 20130224
    └── 20141224

各ディレクトリの最新日付のファイルをカレントディレクトリ(a,b,cのあるディレクトリ)にコピーしてください。各ディレクトリの最新ファイルの日付はそれぞれ違い、コピーの際に衝突しないこととします。

Q7

Q6について、適当にファイルをtouchします。今度はタイムスタンプが最新のファイルを、a, b, cそれぞれからカレントディレクトリにコピーしてください。コピーの際にタイムスタンプを変えない事。

Q8

次のように5個ファイルを作ります。file1をfile2, file2をfile3, file3をfile4, file4をfile5, file5をfile1にmvしてください。

uedambp:q8 ueda$ for i in 1 2 3 4 5 ; do echo $i > file$i ; done
uedambp:q8 ueda$ head *
==> file1 <==
1

==> file2 <==
2

==> file3 <==
3

==> file4 <==
4

==> file5 <==
5
Pocket
LINEで送る

排他を実現するコマンドflock(1)の使い方メモ

Pocket
LINEで送る

排他をかけるコマンドです。Ubuntuので試しました。

まず、排他区間を設けて処理したい内容をシェルスクリプトにします。ここでは、ひたすらプロセス番号をhogeというファイルに書き続けるシェルスクリプトchild.bashを準備しました。

ueda@remote:~$ cat child.bash 
#!/bin/bash

for i in {1..10000} ; do
	echo $$ >> hoge 
done

続きを読む 排他を実現するコマンドflock(1)の使い方メモ

Pocket
LINEで送る

【本番資料】第10回シェル芸勉強会

Pocket
LINEで送る

他の回の問題はこちら

シェル芸勉強会スライド一覧

イントロ

続きを読む 【本番資料】第10回シェル芸勉強会

Pocket
LINEで送る

【問題集】第10回シェル芸勉強会

Pocket
LINEで送る

他の回の問題はこちら

シェル芸勉強会スライド一覧

続きを読む 【問題集】第10回シェル芸勉強会

Pocket
LINEで送る