第36回シェル芸勉強会に参加しました。

第36回シェル芸勉強会の参加レポートです。
最近あまりシェル芸してないからか、別解を思いつかなかったり、似たような解法を使ったりしてますね・・・・・・

参考リンク等

午前の部の資料


午前の部

午前の部は文字コードと銘打たれていましたが、文字コードの前提となる知識みたいな内容でした。次回に続くそうです。

locale

こういう遊びを知りました。


iconv

文字コードを変換する iconv の紹介がありました。

Windows-31J と仲良くするために日常的に iconv -t cp932 を使っている自分には馴染み深いコマンド。
TL を眺めていると、文字コードには nkf を使ってる人が多いみたいでした。

バイナリをテキストに変換する方法

文字コードはもちろんバイナリなので、シェル芸で扱うにはテキスト化する必要があります。
講師の鳥海さんは od や hexdump も紹介されておられましたが、正味ふだん使っている xxd しか覚えてません・・・・・・

xxd -psxxd -r -p が逆変換になってくれてるのが個人的にすごい嬉しい。

基数変換

bc の基数変換は私は良く使ってますが、その他に bash 組み込みの $(()) でも基数変換できるそうです。

$ echo $((25#1000))
15625


私の問題と解答

Q1 は全然わからなくて断念しました。
また、Q8 も途中まではいい感じでしたが結局最後までは行けず・・・

Q1

welcome.txt に隠されたメッセージを読み取ってください。また、welcome.txtワンライナーで作ってみてください。

A1 (断念)
cat welcome.txt | xxd -ps | tr -d \\n | fold -w 84 | awk 1

f:id:nogiro_iota:20180711222005p:plain

解説

cat で見ると、アンダースコアしか存在しない不思議なファイルだったので、とりあえず xxd -psしてみました。

すると 0x5f と 0x00 からなるファイルだとわかったので、幅を調整すれば絵が出そうだと当たりを付けました。

最終行まで幅がすべて同じである保証はありませんが、揃っている方がきれいだと思ったので、揃う幅(因数)を知るために factor を通しました。

$ cat welcome.txt | wc -c | factor
1680: 2 2 2 2 3 5 7

3*7 の倍数を中心に追ってる間に時間切れ。

感想

toilet の出力が幅 70 文字だから幅は 70文字ということでした。
figlet も toilet もあまりちゃんと使ったことがないのが仇になりました。

Q2

次のファイル群について、全てファイル名を N年M組.docNは半角数字、Mは半角大文字)に揃えて徳を積んでください。

$ ls
1-B.doc     3年C組.doc  3年A組.doc  1ーC.doc    4年C組.doc
1A.doc      4年a組.doc   3年B組.doc  1ーD.doc
3年D組.doc  5年A組.doc   4年B組.doc  1年E組.doc

A2
cd worst/ && ls | sed p | sed '0~2y/1234ABCDEFal/1234ABCDEFA1/' | sed '0~2s#[^1-5A-F]##g' | sed '0~2s#\(.\)\(.\)#\1年\2組.doc#' | paste -d\  - - | sed 's#.*#mv &#' | sh 

https://twitter.com/nogiro_iota/status/1015453781394223104

解説

| sh に投げれば何でもできる」系の解き方をしてます。
というか mv のような破壊的な処理を扱うときは、最終的に実行されるスクリプトを眺めるために、 | sh に投げれる形に積極的にしたいですね。

今回は汚い元データとキレイになりつつある成形中データを同一のパイプラインに流して処理しています。
やり方としてはすべて sed で偶数行だけ (0~2) 処理したあとに paste - - で横に並べています。

キレイなデータを作る部分は概ね sedy コマンドで置き換えています。
マルチバイト文字が含まれていると tr でできないのがちょっと面倒ですね・・・・・・

感想

いま考えると 1 B のように空白の入っているファイル名があったらちゃんとできてないですね。
そういう場合だったとすると paste の前に sed 's%.*%"&"%' を挟むとうまくいきます。

Q3

2018年のすべての日付について、2,3,5,7が4つ含まれる日付を列挙してください(例: 2018年3月22日など)。

A3.1
echo 2018{01..12}{01..31} | tr \  \\n | xargs -n 1 date +%Y%m%d -d 2> /dev/null | sed p | sed '0~2s%[^2357]%%g' | awk 'length($0)==4{print p}{p=$0}'

https://twitter.com/nogiro_iota/status/1015457900951568384

解説

日付を列挙するのにはイディオムがあって以下のコマンドで出ます。

echo 2018{01..12}{01..31} | tr \  \\n | xargs -n 1 date +%Y%m%d -d 2> /dev/null

2,3,5,7 の抽出部分は Q2 と似たようなやり方を使っています。
事前に sed p で行を 2 つにしておいて、0~2 で偶数(後ろの方の)行だけから 2,3,5,7 を抽出します。

そうすると文字幅が 4 である行の前の行が答えになるので、 awk で抜き出しています。

A3.2
echo 2018{01..12}{01..31} | tr \  \\n | xargs -n 1 date +%Y%m%d -d 2> /dev/null | python3 -c 'import functools;any(print(i) if(functools.reduce(lambda a,b:a+i.count(b), ["2","3","5","7"], 0) == 4)else 0 for i in (input() for null in iter(int, 1)))'

https://twitter.com/nogiro_iota/status/1015466634222292992

解説

Python ワンライナーが得意そうな問題だったのでやったらできました。

3 項演算子で、入力文字列中の 2,3,5,7 の数の和が 4 だったら出力し、4でなかったら何もしないという分岐を行っています。
Python のリスト内包表記ワンライナーらしさが出ています。

reduce() を使わず i.count("2") + i.count("3") + ... とやると Twitter の文字制限に引っかかったので、重複部分を削るのはやっぱり大事です。
※ 執筆中に reduce()sum(map()) に置き換えたほうが見やすいし、import もなくなるので圧倒的に短いことに気づきましたが・・・

functools.reduce(lambda a,b:a+i.count(b), ["2","3","5","7"], 0) == 4

sum(map(lambda a:i.count(a), ["2","3","5","7"])) == 4
感想

dateutils という日付を扱うためのコマンドがあるらしく、イディオム的に出力処理を作った日付部分を代替できるみたいです。
他にも日付の足し算などもできるみたいで普通に便利そう。

Q4

俳句を考え、次の短冊に縦書きで入れてください。

$ cat tanzaku
┏ ーー-┷-ーー┓
┃       ┃
┃       ┃
┃       ┃
┃       ┃
┃       ┃
┃       ┃
┃       ┃
┗ーーーーーー┛
A4
echo あいうえお かきくけこさし すせそたち | grep -o . | awk 'BEGIN{r=1;c=6}/ /{--c;r=1}!/ /{print ++r"s%.%"$0"%"c}' | sed -f - tanzaku

https://twitter.com/nogiro_iota/status/1015462783352107008

解説

最後の sed -f - tanzaku に渡す前の出力を見れば目指すところがわかりやすいかと思います。

$ echo あいうえお かきくけこさし すせそたち | grep -o . | awk 'BEGIN{r=1;c=6}/ /{--c;r=1}!/ /{print ++r"s%.%"$0"%"c}' | head -n 6
2s%.%あ%6
3s%.%い%6
4s%.%う%6
5s%.%え%6
6s%.%お%6
2s%.%か%5

(17行は長いので列の変わる 6 行目まで出力しています。)

これがなんなのかは下の感想をご確認ください。(感想ってなんだ???)

感想

これはもう、昔やったことあるから解けたみたいな感じです。
昔下のようなビットマスクの 2 次元版みたいな処理がしたかったときがあって、


(2 回めの cat file1はコピペミスです)

下のようなことをしてました。


見やすくすると下のようになります。

$ cat a
abcd
efgh
$ echo -e '0010\n1010' | sed 's/./& /g' | awk '{for (i=1;i<=NF;i++){if ($i=="0"){print NR"s_._0_"i}}}' | sed -f - a
00c0
e0g0

最後の sed -f - a をなくしてみるとわかるのですが、

$ echo -e '0010\n1010' | sed 's/./& /g' | awk '{for (i=1;i<=NF;i++){if ($i=="0"){print NR"s_._0_"i}}}'
1s_._0_1
1s_._0_2
1s_._0_4
2s_._0_2
2s_._0_4

これは、一番左の数字が縦軸で、一番右の数字が横軸の文字を 0 に置き換える sed コマンドを生成しています。

今回は tanzaku ファイルに文字の埋まったデータがあるので、この縦軸・横軸を入れ替えてやれば tateyoko コマンドみたいなことができるわけになります。

それはそれとして until 検索が便利ですね。
from:nogiro_iota * until:2015-11-26 - Twitter Search

Q5

cowsayの牛を右向きにして吹き出しの位置を調整して下さい。(万が一右向きオプションがあったら、それは使わずにお願いします。)

              ________________________________
             < あなたとJava今すぐダウンロード >
              --------------------------------
               ^__^   /
       _______/(oo)  /
   /\/(       /(__)
      | w----||
      ||     ||
A5
cowsay あなたとjava今すぐ | sed 's#$#                         #' | sed 's#^\(.\{30\}\).*#\1#' | rev | tr '\\/()' '/\\)(' | awk 'NR!=2{print}NR==2{for(i=length;i>0;i--){printf(substr($0,i,1))}print ""}' | sed '2s%\(.*>\)\( *\)$%\2\1%'

https://twitter.com/nogiro_iota/status/1015472438652440576

解説

まず、後ろをパディングして、幅を揃えてから反転します。
それだけだとメッセージ部分も反転してしまうので、メッセージ部分を別個に反転しなおして、空白を追加した分吹き出しがずれるので移動しています。

感想

sede コマンドを使えばスッキリ書けたと思うのですが、書き方がわからず・・・・・・
メッセージ部分の空白が、全角文字でズレてるのが若干嫌ですね。

Q6

seq 20の出力について、次のように素数を丸囲みしてください。

1
②
③
4
⑤
6
⑦
8
9
10
⑪
12
⑬
14
15
16
⑰
18
⑲
20
A6
seq 20 | factor | awk '{gsub(":","",$1)}NF==2{print "echo '\''print \"246\";"$1"'\'' | cat <(echo obase=16) - | bc | sed '\''s%^2461%247%'\'' | xxd -r -p | iconv -f utf16be | awk 1"}NF!=2{print "echo "$1}' | bash

https://twitter.com/nogiro_iota/status/1015480448590360576

解説

① の Unicode コードポイントを見ると、 0x2461 (間違ってます。正しくは 0x2460)だったので、16 進数にに変換してから 246 を前に付ければ望む結果が得られます。
ただし、16 以上の数は桁上りがあるので、 5 桁になっているものは置換して対応しました。

UTF-16 エンコードのときにサロゲートペアを含まない Unicode は iconv で utf16be にエンコード/デコードすると xxd で変換/逆変換したときに扱い易いです。

感想

最初は Unicode の結合文字である U+20DD (前の文字を丸囲いする文字)をつければいいかと思い、それがどのように見えるか確認しました。


しかし、環境によるのかも知れませんが別々の文字として表示され ② などにならなかったので別のアプローチにしました。
いま考えると、もしできていたとしても ⑪ などの 2 桁のものは 1① になってしまっていたということになるので、どのみち正解にはなりませんでした。

あと、① → 0x2460 で 0x2461 ではなかったために 1 ズレてしまっていました。
正しい解は下になります。

seq 20 | factor | awk '{gsub(":","",$1)}NF==2{$1=$1"-1";print "echo '\''print \"246\";"$1"'\'' | cat <(echo obase=16) - | bc | sed '\''s%^2461\\([0-9]\\)%247\\1%'\'' | xxd -r -p | iconv -f utf16be | awk 1"}NF!=2{print "echo "$1}' | bash

Q7

text には、文字や空白、改行として認識されないバイナリが含まれています。どの行にどんなものがあるか調査してください。

A7
cat text | iconv -t utf16be | xxd -ps | fold -w 4 | grep ^0 | tr -d \\n | sed 's#000a#\n#g' | nl -nln -ba

https://twitter.com/nogiro_iota/status/1015483095007367169

解説

Unicode コードポイントに変換しながら見ていくと、grep -v ^3 などをして普通に見える文字を削っていった結果、残ったのが grep ^0 でした。

感想

こんなことをのたまってましたが、tr はマルチバイト文字を扱えないので、制御文字もマルチバイト文字も一緒くたに扱っているだけですね。

sed でやれば普通に出てきました。

$ cat text | sed 's/[[:print:]]//g' | xxd -ps
000a0a0a0a020a0a0a060a0a160a0a0a0a0a0a0a0a

Q8

$ echo 嘘は嘘であると見抜ける人でないと(掲示板を使うのは)難しい

から始めて、出力で次のようにルビを打ってください。多少ずれたりスペースが入っても構いません。

ウソ  ウソ        ミヌ      ヒト          ケイジバン  ツカ        ムズカ
嘘は嘘であると見抜ける人でないと(掲示板 を使うのは)難しい
A8 (途中)
echo 嘘は嘘であると見抜ける人でないと(掲示板を使うのは)難しい | pee cat 'mecab | sed '\''/^[はであとでなをの]/!s#.*,##'\'' | sed '\''s%[[:space:]].*%%'\'' | sed '\''/^[はであとでなをの()]/s#.# #g'\'' | sed \$d | tr -d \\n | awk 1' | tac

https://twitter.com/nogiro_iota/status/1015488362214387713

感想

全角カタカナを半角カタカナにしたりする nkf のオプションが覚えきれない。。。と思ってるうちに終わっていました。

元の文と揃える方法は全く考えずに進めていて、column -t も初めて見るものだったので色々新鮮でした。

これ楽しいですね。

$ echo これは、一番左の数字が縦軸で、一番右の数字が横軸の文字を 0 に置き換える sed コマンドを生成しています。 | mecab | sed 's#,[^,]*$##' | sed 's#\t.*,# #' | nkf --hiragana | sed 's%\(.*\) \(.*\)\1%\1 \2%' | sed \$d | awk 'NF==1{$2="*"}{a1[NR]=$1;a2[NR]=$2}END{for(i=1;i<=NR;++i){printf a1[i]" "}print "";for(i=1;i<=NR;++i){printf a2[i]" "}print ""}' | pee 'nkf --katakana | nkf -Z4' cat | sed '2,3!d' | column -t | sed 's%  \([^ ]\)%\1%g' | sed 's%\*% %g'
        イチバンヒダリ  スウジ  タテジク    イチバンミギ  スウジ  ヨコジク  モジ      オキカ                    セイセイ
これは、一番 左  の数字が縦軸 で、一番 右 の数字が横軸 の文字を0に置き換えるsedこまんどを生成しています。

第35回シェル芸勉強会 - 解答(Python ワンライナー版)

前記事はこちら

LT 大会をリモートで見ていても暇だったので、(解ける問題だけ)最近ハマっている Python のリスト内包表記ワンライナーで解きました。
(もともと Python ワンライナー縛りでやる予定だったのですが 1 問目が Python ワンライナーだけでやるには面倒なものだったので忘れてました。)
Q1、Q4 以外を解きました。

リスト内包表記の基本

記法は [<expression> for <variable> in <iterable>] です。
<iterable> にイテレーション可能なオブジェクトを指定すると、その各要素が <variable> に格納されて <expression> の結果からリストが作られます。

$ python3 -c 'print([i**2 for i in range(5)])'
[0, 1, 4, 9, 16]
$ python3 -c '[print(i**2) for i in range(5)]'
0
1
4
9
16

[print(i**2) for i in range(5)] のように、<expression> に処理を書くと、ループのように扱えます。
Iterable なオブジェクトを順次実行できるので、実質リストである標準入力も簡単に扱えます。

リスト内包表記そのものは以下の 2 記事がわかりやすかったです。この記事ではこれ以上詳しくは説明しません。
pythonの内包表記を少し詳しく - Qiita
リスト内包表記の活用と悪用 - Qiita

Python ワンライナーで使うときの基本の基本

そもそもなぜリスト内包表記を使うかというと、Python ワンライナーで便利だからです。
ワンライナーPython を使うには python -c 'script' で script 部分を指定しますが、ループを使うためにリスト内包表記が必要です。(表現力は map と変わらないのでそちらでもいいのですが、個人的にリスト内包表記のほうが好きだし早いらしいです。)

Python ワンライナー+リスト内包表記で標準入力を扱う

Python 3 の標準入力といえば input() ですが、input() は 1 行しか扱えません。([i for i in input()]すると、Python では文字列はリストのように扱えるので、i には 1 行目の各文字が入ります。)
input() で複数行を得るには↓のようにする必要があります。

$ seq 10 | python3 -c '[print(input()) for i in range(3)]'
1
2
3

このやり方では、実行結果を見てもわかるように、読み込み行数を range() に固定で与える必要があります。
[2018/04/11 追記]
ジェネレーター式・iter()any()を使えば無限ループができることがわかったので、以下のようにすれば input() で、すべての標準入力が得られます。

$ seq 3 | python3 -c 'any(print(i) for i in (input() for null in iter(int,1)))' 2> /dev/null
1
2
3

ただし注意点が 1 つあって、標準入力がなくなって input() が例外を投げて終了するので、Python の終了コードはエラー (1) になります。
[2018/04/11 追記終わり]

標準入力からすべての行を得るには↓のように、<iterable>を sys.stdin にします。

$ seq 3 | python3 -c 'import sys; [print(int(i)) for i in sys.stdin]'
1
2
3

改行が入るので int にキャストしていますが、処理(キャストするなど)を挟めば改行は消えるのでだいたい気にしなくていいです。

余談ですが、個人的には CSV の処理が楽です。↓ は一列目の切り出しです。

$ echo $'a,b\n1,2' | python3 -c 'import sys,csv;[print(i[0]) for i in csv.reader(sys.stdin)]'
a
1

改行の入っている要素のある CSV の処理に良く使っています。

問題と解答

Q2

次のようなファイルheroheroがあります。

$ cat herohero 
1へ
7ろ
9へ
13ろ

ひらがなを左側に書いてある数字の行に持って行き、次のような出力に変換してください。

へ





ろ

へ



ろ
A2
nkf -Z herohero | sed 's#[0-9]*#& #' | python3 -c 'import sys;d={int(i.split()[0])-1:i.split()[1] for i in sys.stdin};[print(d[i]) if i in d else print() for i in range(max(d.keys())+1)]'

https://twitter.com/nogiro_iota/status/982563903669338118

解説

全角から半角の変換は Python ではめんどくさいので、前処理はシェル芸でします。

$ nkf -Z herohero | sed 's#[0-9]*#& #'
1 へ
7 ろ
9 へ
13 ろ

ディクショナリ内包表記(リスト内包表記の辞書版) d={int(i.split()[0])-1:i.split()[1] for i in sys.stdin} で前処理済データを変数に格納します。

その後、[print(d[i]) if i in d else print() for i in range(max(d.keys())+1)] で、数字の最大数 (max(d.keys())+1)) 回数ループして、3 項演算子 (ifTrue if expression else ifFalse) で入力と空白の処理を分けています。
(記事を書いていて気づきましたが print(d[i]) if i in d else print()print(d[i] if i in d else "") に書き直せますね。)

感想

FizzBuzz ちっくに解けました。

Q3

次のようなファイルdataがあります。

$ cat data
1 A
1 B
2 C
2 C
1 B
3 C
4 C
3 B
3 B
3 D
3 B
1 B
2 A
1 A
2 C

集計して次のような出力を得てください。

1 A:2 B:3
2 A:1 C:3
3 B:3 C:1 D:1
4 C:1
A3
cat data | python3 -c 'import collections,sys;d={};[d[i2[0]].append(i2[1]) if i2[0] in d.keys() else d.update({i2[0]:[i2[1]]}) for i2 in [tuple(i.split()) for i in sys.stdin]];[print(i,dict(collections.Counter(d[i]))) for i in d.keys()]' | sort

https://twitter.com/nogiro_iota/status/982573747440791552

解説

collections.Counter を使うと、リスト内の要素の数を数えることができます。

$ python3 -c 'import collections;a=[int(i**1.3)%5 for i in range(10)];print(a);print(collections.Counter(a))'
[0, 1, 2, 4, 1, 3, 0, 2, 4, 2]
Counter({2: 3, 0: 2, 1: 2, 4: 2, 3: 1})

この出力を整形しています(中途半端ですが)。

感想

collections.Counter 知らなかったんですが、解くためにググって新しいことが知れたのが良かった。

Q5

echo 響け!ユーフォニアム からはじめて、次のような出力を得てください。なお、出題者はこのアニメを見たことがありません。

響け!ユーフォニアム
 響け!ユォニアム
  響け!ニアム
   響けアム
    響ム
     
     
    ム響
   ムアけ響
  ムアニ!け響
 ムアニォユ!け響
ムアニォフーユ!け響
A5
echo 響け!ユーフォニアム | python3 -c '[(lambda k:[print(l[::j]) for l in k])(s[::j]) for s in [[" "*i+x[:int(len(x)/2)-i]+x[int(len(x)/2)+i:]+" "*i for x in [input()] for i in range(int(len(x)/2)+1)]] for j in [1,-1]]'

https://twitter.com/nogiro_iota/status/982577064900349954

解説

入力は for x in [input()] で行っています。これは、リスト内包表記内で変数を代入する方法で、今考えると別に外出しで x=input(); すれば良かったです。

前半の三角部分は Python のスライスで " "*i+x[:int(len(x)/2)-i]+x[int(len(x)/2)+i:]+" "*i すると作ることができます。
さらに Python のスライスは、ステップ数を指定することができ、-1 を指定すると逆順になるので、前半部分を2次元リストにしておくと後半部分は簡単に作成できます。

感想

ステップ数を [1,-1] で与えることに気づいたときは「この問題は Python ワンライナーのためにあったのでは???」という気分になりました。

ひらけ!ポンキッキ」はこんな感じになりますが、たしかに中央が謎です。

$ echo ひらけ!ポンキッキ | python3 -c '[(lambda k:[print(l[::j]) for l in k])(s[::j]) for s in [[" "*i+x[:int(len(x)/2)-i]+x[int(len(x)/2)+i:]+" "*i for x in [input()] for i in range(int(len(x)/2)+1)]] for j in [1,-1]]'
ひらけ!ポンキッキ
 ひらけンキッキ 
  ひらキッキ  
   ひッキ   
    キ    
    キ    
   キッひ   
  キッキらひ  
 キッキンけらひ 
キッキンポ!けらひ

Q6

素因数分解したときに23より大きい素因数を持たない自然数を1985個抽出してください。

A6
sudo pip3 install sympy
python3 -c 'import sympy;[print(y[0]) for y in [j for j in filter(lambda x:23>=max(x[1].keys()),[(i,sympy.factorint(i)) for i in range(2,21000)])][:1985]]'

https://twitter.com/nogiro_iota/status/982582064024186881

解説

まず sympy をインストールします。(シェル芸勉強会は環境構築から)

$ sudo pip3 install sympy

sympy.factorint() が factor 的をしてくれるので、そこから作ります。(戻り値が素因数がキーで値がその数になってるのがちょっと違います。)
(必要な量がわかっていたので)21000 までの素数すべてを filter しています。

感想

素数ジェネレーターないからめんどくさいかと思ったらあったりしました。
https://twitter.com/nogiro_iota/status/982577420107628545
https://twitter.com/nogiro_iota/status/982579286136709120

Q7

素数番目の文字を抽出すると意味のある語句になっているような文字列を作成してください。例を示します。(素数番目でない文字は特に凝る必要はありません。同じ文字でも大丈夫です。)

うそすんうんだいうんいんすんこうき

その後、その語句を抽出してください。

A7
echo 響け!ユーフォニアム | python3 -c 'import sympy;x=input();p=[i for i in range(2,int(5*len(x)**1.1)) if list(sympy.factorint(i).values())==[1]][:len(x)];print("".join([x[p.index(i)] if i in p else "あ" for i in range(1,1+max(p))]))'

https://twitter.com/nogiro_iota/status/982590950940655616

解説

最終的に必要になる文字数がわからなかったので、↓で調べてました。

yes '' | nl -ba -nln | factor | awk 'NF==2{print $2}' | awk '{print $0/NR}'
yes '' | nl -ba -nln | factor | awk 'NF==2{print $2}' | awk '{print $0/(NR**1.09)}'

1行目だと単調増加して、2 行目だと単調減少したので、 とりあえず「定数*入力文字数^1.1」を用意しておけば足りそうです。(定数は 5 でした。)

入力文字列以上の長さの素数のリストを用意して、「定数*入力文字数^1.1」の回数ループして 3 項演算子で A2 同様に解きます。
入力文字のインデックスと素数リストのインデックスは対応しているので、3 項演算子で入力を出力するときはそれで引っ張ってきます。

感想

解説のしにくさがすごい。

Q8

Q6の方法で作成した自然数をファイルaに保存し、この中から4つ数字を選んで掛け算したとき、その値がある自然数の4乗になっている組み合わせを1個以上探してください。

A8.1
cat a | python3 -c 'import re,math,functools,sys,itertools;[print("*".join(map(str,list(j)))) for j in itertools.combinations([int(i) for i in sys.stdin],4) if re.sub("\.0+$","",str(math.sqrt(math.sqrt(functools.reduce(lambda a,b:a*b,j,1))))).isdigit()]'

https://twitter.com/nogiro_iota/status/982556088376614913

解説

itertools.combinations() で 4 つの組み合わせをすべて列挙できるので、掛け算して 4 乗根が整数かどうかで判定します。

A8.2
cat a | python3 -c 'import math,sys,itertools;[print("*".join(map(str,list(j)))) for f in [lambda x:x==float(int(x))] for j in itertools.combinations([int(i) for i in sys.stdin],4) if f(math.sqrt(math.sqrt(j[0]*j[1]*j[2]*j[3])))]'

https://twitter.com/nogiro_iota/status/982852367249846273

解説

A8.1 から整数判定の処理を変更したり、掛け算の仕方を変えたりしています。

感想

4 乗根が整数かどうかで判定していますが sympy.factorint() も使えますね。import が増えると Twitter に載せられなくなりますが。。。

第35回シェル芸勉強会 - 解答(リモート参加)(普通版)

第35回シェル芸勉強会で寝坊してリモート参加しました。
ドタキャンして申し訳ありません。。。

問題と私の解答

Q1

curl parrot.live で表示されるオウムをファイルに生け捕りにして、ファイルから再生してください

A1
timeout 10 curl http://parrot.live  > a; seq `wc -l a | cut -d\  -f1` | sed 's#.*#sed &\\!d a#;0~18s#$#;sleep 0.0001#' | sh

https://twitter.com/nogiro_iota/status/982474241588150277
https://twitter.com/nogiro_iota/status/982474858209558528

解説

curl で parrot.live を叩くと無限に踊ってくれるので、timeout を使って途中で止めます。
最後の | sh までは ↓ のように各行を出力しながら sleep するスクリプトを生成しています。

$ seq `wc -l a | cut -d\  -f1` | sed 's#.*#sed &\\!d a#;0~18s#$#;sleep 0.0001#' | head -n 20                      sed 1\!d a
sed 2\!d a
sed 3\!d a
sed 4\!d a
sed 5\!d a
sed 6\!d a
sed 7\!d a
sed 8\!d a
sed 9\!d a
sed 10\!d a
sed 11\!d a
sed 12\!d a
sed 13\!d a
sed 14\!d a
sed 15\!d a
sed 16\!d a
sed 17\!d a
sed 18\!d a;sleep 0.0001
sed 19\!d a
sed 20\!d a
感想

他の方々の解答によると、画面クリア・色変えの制御コードのある行の前でスリープするとキレイに表示できたようです。
Mac OS X だとファイルに書き出すのが難しいらしいですが、WSL (Ubuntu 16.04) だったため普通にリダイレクトできました。

Q2

次のようなファイルheroheroがあります。

$ cat herohero 
1へ
7ろ
9へ
13ろ

ひらがなを左側に書いてある数字の行に持って行き、次のような出力に変換してください。

へ





ろ

へ



ろ
A2.1
cat herohero | sed 'y#0123456789#0123456789#' | sed 's#^[0-9]*#& #' | awk 'BEGIN{i=1}{for (;i<=$1;++i){print ""}printf $2}' | sed 1d | awk 1

https://twitter.com/nogiro_iota/status/982476301880606720

解説

awk の for 文で指定数字の前までに空行を出力しています。指定数字があったらそれを出力します。
(ちなみに sed の y コマンドで全部の数字を列挙しているのは、一般性を気にしてるのではなく読むのがめんどくさかったからです。)

A2.2

| sh すればなんとかなる」版です。

nkf -Z herohero | sed 's#^[0-9]*#& #' | sort -Vr | awk 'NR==1{print "yes|head -"$1}{print "sed "$1"s_y_"$2"_"}' | paste -s -d\| | sh | tr -d y

https://twitter.com/nogiro_iota/status/982478641773740032

解説

| sh の前までで↓を出力しています。 yes の出力を消していないので最後に tr で消します。

$ nkf -Z herohero | sed 's#^[0-9]*#& #' | sort -Vr | awk 'NR==1{print "yes|head -"$1}{print "sed "$1"s_y_"$2"_"}' | paste -s -d\|
yes|head -13|sed 13s_y_ろ_|sed 9s_y_へ_|sed 7s_y_ろ_|sed 1s_y_へ_
A2.3
nkf -Z herohero | { tee >(tr -dc 0-9\\n | tac | head -1 | xargs seq); } | sort -n | sed 's#[0-9]*#& #' | tac | uniq -w2 | cut -d\  -f2 | tac

https://twitter.com/nogiro_iota/status/982480115119484928

解説

会長の解説を聞いていて思いついた解答です。
前で sort- <(seq) していたものを{ tee >(seq); } | sortに置き換えました。
{ tee >(command); } でパイプラインを分岐することができますが、順序が不定になります(tac, sort 等の全部読み込んでから動作するコマンドは後から出力されますし、今回は後から sort するのでさらに大丈夫です)。
(後述の pee を使うと順序も決まるし変なイディオムがなくて便利。{} をなんで付けていたかは忘れました。)
seq の引数をマジックナンバーにしないために地味に長くなっています。

感想

A2.2 は sed の c コマンドを使っておけばもうちょっとキレイにかけた模様です。

nkf -Z herohero | sed 's#^[0-9]*#& #' | sort -Vr | awk 'NR==1{print "yes \"\"| head -"$1}{print "sed "$1"c"$2}' | paste -s -d\| | sh

nkf がいつまでも覚えられない。。。

Q3

次のようなファイルdataがあります。

$ cat data
1 A
1 B
2 C
2 C
1 B
3 C
4 C
3 B
3 B
3 D
3 B
1 B
2 A
1 A
2 C

集計して次のような出力を得てください。

1 A:2 B:3
2 A:1 C:3
3 B:3 C:1 D:1
4 C:1
A3
cat data | sort | uniq -c | sort -k2 | awk '$2!=p2{print p2}{printf $3":"$1" "}{p2=$2}END{print p2}' | sed -E 's#(.*) ([0-9])$#\2 \1#'

https://twitter.com/nogiro_iota/status/982482659711463424

解説

順番を保証しないとダメなのかと思ったため、連想配列を使わず 2 行ずつ見て改行しました。

Q4

ひらがなで名前っぽい単語をランダムに生成してみてください。

A4.1
w3m -dump http://douseidoumei.com/heisei/h_name/15/001.htm | sed '1,/名前リスト/d' | sed '1,3d' | awk '$0=$3' | mecab | awk -F, 'NF!=1{printf $NF}NF==1{print ""}' | nkf --hiragana | sed \$d

https://twitter.com/nogiro_iota/status/982486297670373376

解説

思いつかなかったので、Web スクレイピングで名前を拾って来ました。

感想

mecab を使うという発想がまず出なかった。。。

Q5

echo 響け!ユーフォニアム からはじめて、次のような出力を得てください。なお、出題者はこのアニメを見たことがありません。

響け!ユーフォニアム
 響け!ユォニアム
  響け!ニアム
   響けアム
    響ム
     
     
    ム響
   ムアけ響
  ムアニ!け響
 ムアニォユ!け響
ムアニォフーユ!け響
A5.1
echo '響け!ユーフォニアム' | awk '{l=length;for(i=0;i<=l/2;++i)print "echo "$0"|sed -E \"s#(.{"l/2-i"}).{"i*2"}#\\1#\""}' | sh | awk 'NR==1{l=length/2}{for(i=1;i<NR;i++){printf " "}print}' | { tee >(tac|rev); } | sed -E 's#([^ ]+)( +)#\2\1#'

https://twitter.com/nogiro_iota/status/982494450722136064

解説

折返し反転をしている { tee >(tac|rev); } 部分以外は awk でゴリ押ししました。
前述のとおり tac が入っているため順番どおりに出ているようです。
なお、解答者もこのアニメを見たことがありません。

A5.2
echo '響け!ユーフォニアム'|awk '{l=length;for(i=0;i<=l/2;++i)print "echo "$0"|sed -E \"s#(.{"l/2-i"}).{"i*2"}#\\1#\""}'|sh|awk 'NR==1{l=length/2}{for(i=1;i<NR;i++){printf " "}print}'|vim -es /dev/stdin +%p +'1,$!tac|rev' +'%p|q!'|sed -E 's#([^ ]+)( +)#\2\1#'

https://twitter.com/nogiro_iota/status/982495788260405248

解説

折返し部分を vim シェル芸に置き換えました。出力を分けてできるので非常に便利でした。

感想

pee というコマンドが moreutils にあるらしく、tee >(command)pee cat command と書くことができて、出力順序まで指定できました。
↓ わかりがあります。


Q6

素因数分解したときに23より大きい素因数を持たない自然数を1985個抽出してください。

A6
yes | nl -nln | cut -f1 | factor | awk '$NF<23' | head -n 1985 | cut -f1 -d: | head

https://twitter.com/nogiro_iota/status/982498968838914048

感想

ちょうど↓の記事を読んでいたので1人で盛り上がってました。
鳩ノ巣原理を使う数学オリンピックの問題 | 高校数学の美しい物語

factor で出力される素因数は右側が一番大きいの知らなかった。。。

Q7

素数番目の文字を抽出すると意味のある語句になっているような文字列を作成してください。例を示します。(素数番目でない文字は特に凝る必要はありません。同じ文字でも大丈夫です。)

うそすんうんだいうんいんすんこうき

その後、その語句を抽出してください。

解なし

勉強会中には解答は思いつきませんでした。

感想

「ゴリ押しするしかないかなー」と思っていたら次の問題に入ってました。

Q8

Q6の方法で作成した自然数をファイルaに保存し、この中から4つ数字を選んで掛け算したとき、その値がある自然数の4乗になっている組み合わせを1個以上探してください。

A8
yes 'shuf a|head -n 4|paste -s -d\*|sed '\''s%.*%print "&\\n";&%'\''|bc|sed '\''1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'\'|sh|tr \" \'|sh|awk 'p~"*"&&$0~"*"{print p;exit}{p=$0}'

https://twitter.com/nogiro_iota/status/982509662233743360
https://twitter.com/nogiro_iota/status/982512441736101888

解説

「ファイル a からランダムに4つ数を抽出して、それらの掛け算の計算式そのものと計算結果を bc で出力、計算式は echo して、計算結果は factor 後に素因数を 4 列にして行内が全部一緒だったら消すコマンドを生成する」というコマンドを yes で無限に生成して、2回 | sh しています。その後に、(計算式が連続している場合は 4 つ組の素因数が全部おなじだったということなので)計算式が連続していたら上の計算式を出力します。
長すぎて解説になってませんね。

1 つ目の | sh の前の出力は以下のとおりです。

$ yes 'shuf a|head -n 4|paste -s -d\*|sed '\''s%.*%print "&\\n";&%'\''|bc|sed '\''1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'\' | head -n 2
shuf a|head -n 4|paste -s -d\*|sed 's%.*%print "&\\n";&%'|bc|sed '1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'
shuf a|head -n 4|paste -s -d\*|sed 's%.*%print "&\\n";&%'|bc|sed '1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'

その後もすごい泥縄式なので、以下に各段階の出力結果を載せます。

$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%'
print "6885*10143*8415*918\n";6885*10143*8415*918
$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%' | bc
3366*9728*1260*384
15843073720320
$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%' | bc | sed '1~2s%^%echo %;0~2s%.*%factor & | sed "s_^[^:]*: __" | tr " " \\\\n | paste - - - - | awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'
echo 13754*882*7200*2310
factor 201763257696000 | sed "s_^[^:]*: __" | tr " " \\n | paste - - - - | awk "!($1==$2&&$1==$3&&$1==$4)"
$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%' | bc |sed '1~2s%^%echo %;0~2s%.*%factor & | sed "s_^[^:]*: __" | tr " " \\\\n | paste - - - - | awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%' | tr \" \' | sh
4394*9375*598*10944
3       3       3       5
19      23

本来は shuf a | ~は yes で供給され無限回実行されるので、以下の出力は無限回出ます。

4394*9375*598*10944
3       3       3       5
19      23

出力を観察してもらうと、素因数としては 2 が含まれるはずですが、4 つおなじ値があったときは awk "!($1==$2\&\&$1==$3\&\&$1==$4)" で除外しています。
そのため、「ある自然数の4乗になっている組み合わせ」になっている場合素因数はなにも出力されないので、計算式が連続して出力されます。
awk で上の計算式を抽出して終了すれば結果が得られます。

感想

泥縄式すぎて解説できる気がしない。。。

#echo ってツイッターAmazon Echo のアイコンが出てるのは私だけ・・・?
https://twitter.com/nogiro_iota/status/982510122638262273

ボードゲーム草案 - 位置推定タイルプレイスメント

ゲムマ大阪に行っていろいろ刺激があった結果、鍵垢で言ってた「迷子」をモチーフにしたボードゲームがまとまってきたので草案を上げます。

現在考えられる面白み

ゲームは時系列的に大きく2つのステップに分かれ、それぞれ次の面白みがある。

  • 前半: ランダム性が高いが勝利に必要な材料を集めるステップ(ローグライクのようなゲーム性)
  • 後半: ランダム性が少ない勝利点を集めるステップ(詰め将棋のようなゲーム性)

(「こういうのを目指しています」という願望です)

目的

自分の位置・目的地の位置を特定してたどり着く。
⇒ 手札(3枚)の条件を満たすタイルを見つけて、場札(3枚)の条件を満たすタイルを目指す

コンポーネント

  • 地形タイル
  • 位置・アクションカード
    • 位置情報とアクションは1枚のカードに併記される
  • 勝利点カード
  • 自コマ

初期設定

  • 地形タイルを場に1枚配置する。
    • タイルの残りをタイルデッキとする。(場にN枚公開する、かも)
  • 位置・アクションカードを各プレイヤーに3枚配り、場に3枚公開する。
    • カードの残りはひとまとめにしてカードデッキとする。

ゲーム全体の流れ

各プレイヤーが順番に手番を行う。
大まかな流れは、タイルを配置してマップを拡張しながら、自分の初期位置を決定し、目的地を満たすタイルまで移動すること。
手番の詳細は「手番の行動」に記載した。

手番の行動

「自分の位置がわかっているかどうか(自コマが場にあるかどうか)」「位置がわかっているプレイヤーがいるかどうか」により選択できる行動が異なる。

自コマが場にない場合

まず、タイルデッキからタイルを取り、道路が繋がるよう既に配置されたタイルに隣接させて配置する。(場にN枚タイルを公開するメカニクスにする場合は、公開タイルから選んで配置し、タイルデッキから新規にタイルを公開する)
その後、以下の行動を選択する。

  • 手札を1枚捨ててカードデッキから引く
  • (誰のコマも場にない場合)場札と手札を1枚交換する
  • (条件に合うタイルがある場合)手札を公開して、条件に合うタイルに自コマを配置する(自己位置を決定する)
  • (コマを配置している他のプレイヤーがいる場合)手札を切ってアクションを行い妨害する。1枚引く(手札を補充するかどうか(出遅れている場合に位置条件を緩和するかどうか)は要検討)

自コマが場にある場合

まず、上記と同様にタイルを置く。
次に自コマを1タイル分、道路が接続されている方へ移動する。
(バス停などを作って、乗っている間はゴールできないが2マスまで移動、とかしても面白いかもしれない。もちろん、アクションにバス乗っているときのみの妨害を追加する)

終了処理

自コマを場札の条件を満たすタイルに移動したプレイヤーがいる状態であるプレイヤーの手番が終了した。

勝利条件

行動に応じて勝利点カードを得る事ができ、終了条件を満たした段階で点数の合計が高かったプレイヤーの勝ち。
例えば、「自コマを場に出した最初のプレイヤーである (3点)」「自分の手番でゲームが終了した」「目的地にたどり着いた」「目的地に2番目に近い」「一番アクションカードを使った」「ランドマークAに行った」など。
イメージとしては、デジタルゲームのトロフィーがゲームごとに配られて勝利点になる感じ。

コンポーネントの詳細

タイル

タイルの接続規則としては、道路のみを想定している。
タイルにはランドマークが記載されており、位置条件として用いられる。

  • ランドマークは以下の3種類くらいのレアリティを想定
    • ユニーク(電波塔、競技場、神社など
    • アンコモン(高層オフィスビル、高層マンションなど
    • コモン(赤い屋根の家、コンビニ、スーパーなど

位置・アクションカード

位置・アクションカードは「位置情報」「アクションの効果」の2つの情報を持つ。
「位置情報」は、タイルに記載されたランドマークとの位置関係を示す。(すべて成立するタイルがある場合に手番で「自己位置を決定」できる。)

  • ランドマークのレアリティごとの位置情報例
    • ユニークの前・足元(同じタイル)
    • アンコモンの近傍(隣接マス)
    • コモンと同じ道にいる(直線上で途切れていない道にコモンがある)
  • アクション例
    • 場のコマを任意の方向へ移動
    • (場札に関わらない)タイルを1枚除去
    • etc (バリエーションが全然足りない。。。)

※ 今のところ、アクションはユニーク・アンコモンの位置条件のものにのみに付ける想定

懸念点

「勝ってる人が勝ち続ける問題」「キングメーカー問題」の対策として、トロフィーで点数を付ける形を考えてみましたが機能するかは不明です。
また、現在のメカニクス上、一番最初に自コマを配置したプレイヤーが他の全員から集中的に妨害を受けるので、それもトロフィー化することで対策できればいいなと思っています。

前半がソリティアっぽいので「前半要らなくね?」ってならないか気になります。
後半の選択できる行動が少ないように思います。
勝利点カードの管理が非常にめんどくさそうです。

今後の予定

4月半ばくらいまでにモックを作れたらいいな。。。

sudo 後の環境変数を設定する方法たち

調べた結果 4 つの方法でできました。

export ENV=VAL
sudo -E command
  • sudoers に evn_keep を指定する
$ sudo cp /etc/sudoers sudoers.bac
$ sudo visudo
$ sudo diff -u sudoers.bac /etc/sudoers
--- sudoers.bac        2018-03-16 20:28:53.668295300 +0900
+++ /etc/sudoers   2018-03-16 20:29:29.756458100 +0900
@@ -9,6 +9,7 @@
 Defaults       env_reset
 Defaults       mail_badpass
 Defaults       secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
+Defaults       env_keep += "ENV"

 # Host alias specification

$ export ENV=VAL
$ sudo command
sudo bash -c 'ENV=VAL command'
  • sudo 先のユーザーの .bashrc などに設定後、 -i オプションを付けて sudo を実行する。
echo 'export ENV=VAL'  | sudo tee /root/.bashrc
sudo -i command

個人的には、いろいろ試しているときには一時環境変数を使って、ちゃんとコマンドなどを作るときは env_keep を設定するのがいいのかなと思いました。

env_keep で引き継ぐ変数を一覧する方法

sudo sudo -V で確認できます。

改訂履歴

  • 2018/03/16 参考文献・env_keep確認方法の追加

あの名曲 shell32.dll を自分で作る (バイナリにwavヘッダを付ける)

名曲です

www.youtube.com

サンプリングレートなどは動画中にあるので、自分のPCがWindowsであれば、同じDLLがあるのでwavファイルが作れるのでは?
WSLならシェル芸もできる!やったね。

やりかた

シェル芸でゴリッとする。

$ cat shell32.dll | xxd -ps | tr -d \\n | awk '{printf("echo %08x | fold -w2 | tac\n", length/2 + 8 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4); printf("echo %08x | fold -w2 | tac\n", length/2); print "echo "$0}' | sed '2iecho -n data | xxd -ps' | sed '2iprintf %04x 16 | fold -w2 | tac' | sed '2iprintf %04x $((16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x $((8000*16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x 8000 | fold -w2 | tac' | sed '2iprintf %04x 2 | fold -w2 | tac' | sed '2iprintf %04x 1 | fold -w2 | tac' | sed '2iprintf %08x 16 | fold -w2 | tac' | sed '2iecho -n WAVEfmt\\\ | xxd -ps' | sed '1iecho -n RIFF | xxd -ps' | sh | tr -d \\n | xxd -r -p > out.wav

上記のシェル芸が何をしているのかと言うと、バイナリにヘッダを追加しています。
shに2回投げているので見通しが非常に悪いですが、概ねsedでコマンドを作って、shに渡しています。
詳細な説明は次のワンライナーのあとに解説します。

最初の sh に投げる前までの出力の12行目までは以下になっています(13行目はデータ部で非常に長いため削っています)。

$ cat shell32.dll | xxd -ps | tr -d \\n | awk '{printf("echo %08x | fold -w2 | tac\n", length/2 + 8 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4); printf("echo %08x | fold -w2 | tac\n", length/2); print "echo "$0}' | sed '2iecho -n data | xxd -ps' | sed '2iprintf %04x 16 | fold -w2 | tac' | sed '2iprintf %04x $((16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x $((8000*16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x 8000 | fold -w2 | tac' | sed '2iprintf %04x 2 | fold -w2 | tac' | sed '2iprintf %04x 1 | fold -w2 | tac' | sed '2iprintf %08x 16 | fold -w2 | tac' | sed '2iecho -n WAVEfmt\\\ | xxd -ps' | sed '1iecho -n RIFF | xxd -ps' | head -n 12
echo -n RIFF | xxd -ps
echo 0145d114 | fold -w2 | tac
echo -n WAVEfmt\ | xxd -ps
printf %08x 16 | fold -w2 | tac
printf %04x 1 | fold -w2 | tac
printf %04x 2 | fold -w2 | tac
printf %08x 8000 | fold -w2 | tac
printf %08x $((8000*16/8*2)) | fold -w2 | tac
printf %04x $((16/8*2)) | fold -w2 | tac
printf %04x 16 | fold -w2 | tac
echo -n data | xxd -ps
echo 0145d0f0 | fold -w2 | tac

それぞれのprintfechosedで追加しています。
なお、データ長が必要な部分はawkを使用しています。

wav ヘッダのフォーマットは、次のページを参考にしました。
wav ファイルフォーマット
ワンライナーでは各フィールドを下から順に追加しているので、shに投げる前は 3フィールド目の'W' 'A' 'V' 'E'部分から順に並んでいます。

fold -w 2 | tacエンディアンの変換を行っています。
sedi コマンドで追加すると各フィールドごとにエンディアン変換ができるのが個人的に便利だと思っています。

結果

聞いてみるとバージョンが違うのか結構違う音が聞こえてきます。。。ただし、共通している部分もありbin -> wavの変換は上手くできているようです。

まとめ

これで好きなバイナリを音楽として聞く事ができますね!

正攻法 *1

$ ffmpeg -f u16le -ac 2 -ar 8000 -i shell32.dll out.wav


整形

最初のワンライナーの整形版です。
はてなブログ上で作ってるので動くかは保証しかねます。雰囲気用です。

RATE=8000 # サンプリングレート
CHANNELS=2 # モノラル=1,ステレオ=2
BIT_DEPTH=16 # ビット深度 (リニアPCM16bitの16で、データの単位のこと)
cat shell32.dll \
  | xxd -ps \
  | tr -d \\n \
  | awk '{
    printf("echo %08x | fold -w2 | tac\n", length/2 + 8 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4);
    printf("echo %08x | fold -w2 | tac\n", length/2);
    print "echo "$0
  }' \
  | sed '2iecho -n data | xxd -ps' \
  | sed '2iprintf %04x '$BIT_DEPTH' | fold -w2 | tac' \
  | sed '2iprintf %04x $(('$BIT_DEPTH'/8*'$CHANNELS')) | fold -w2 | tac' \
  | sed '2iprintf %08x $(('$RATE'*'$BIT_DEPTH'/8*2)) | fold -w2 | tac' \
  | sed '2iprintf %08x '$RATE' | fold -w2 | tac' \
  | sed '2iprintf %04x '$CHANNELS' | fold -w2 | tac' \
  | sed '2iprintf %04x 1 | fold -w2 | tac' \
  | sed '2iprintf %08x 16 | fold -w2 | tac' \
  | sed '2iecho -n WAVEfmt\\\ | xxd -ps' \
  | sed '1iecho -n RIFF | xxd -ps' \
  | sh \
  | tr -d \\n \
  | xxd -r -p > out.wav


改訂履歴

  • 2017/12/27: ワンライナー中で2回 awk を使って、長さを測っていて、遅かったので修正しました。また、整形したワンライナーの追加・全体的な修正をしました。

自宅仮想環境 - /etc/fstab を sda1 などから UUID に置き換える

前置き

インストール時にテキトーに作った/etc/fstabを昨日修正する予定で、完全に忘れてました。

元の fstab

「とりあえずマウントできればいい」程度で書かれています。

$ cat /etc/fstab
# UNCONFIGURED FSTAB FOR BASE SYSTEM
/dev/sda1 /     ext4 defaults 0 1
/dev/sda2 /boot ext4 defaults 0 2
/dev/sda3 /home ext4 defaults 0 2
/dev/sda4 /var  ext4 defaults 0 2

シェル芸

遭遇したことないので実感はないのですが、sda とかは必ずしも同じになるとは限らないそうなので、 UUID を指定してマウントされるようにします。

lsblk -f で UUID が拾えるので、シェル芸でなんとかします。

$ lsblk -f | awk 'NF==4' | cut -c7- | awk '$0="UUID="$3"  "sprintf("%-5s",$4)"  "$2"  defaults           0 2"' | awk '$2=="/"{gsub("2$",1); gsub("defaults         ","errors=remount-ro")}{print}' | cat <(sed 1\!d /etc/fstab) - | tee fstab.new
# UNCONFIGURED FSTAB FOR BASE SYSTEM
UUID=88c99083-a20f-4b7a-a060-bbcc1387848d  /var   ext4  defaults           0 2
UUID=fdf0bc36-6a23-47f0-996d-57afb199427e  /boot  ext4  defaults           0 2
UUID=fffa2250-e91d-4f69-a26a-88f98e01b508  /home  ext4  defaults           0 2
UUID=e6a82ba3-9564-4771-b51b-c6e8949ec4ea  /      ext4  errors=remount-ro  0 1
$ sudo mv fstab.new /etc/fstab

確認

$ sudo reboot

再起動してちゃんと起きてきたことを確認して、ちゃんとマウントされてることを確認しました。

$ mount | grep sda
/dev/sda1 on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/sda2 on /boot type ext4 (rw,relatime,data=ordered)
/dev/sda3 on /home type ext4 (rw,relatime,data=ordered)
/dev/sda4 on /var type ext4 (rw,relatime,data=ordered)

参考にした文献

fstab - ArchWiki